use semver::Version;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::{HashMap, VecDeque};
use std::env;
use std::fs::{self, File, OpenOptions};
use std::path::{Component, Path, PathBuf};
use std::process::Command;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, Condvar, Mutex, OnceLock, Weak};
use std::thread;
use std::time::Duration;
use crate::runtime::managed_package::{
ManagedFilesystemObjectIdentity, ManagedRuntimePackageContext,
capture_managed_directory_identity, validate_managed_directory_identity,
};
use crate::runtime::path::{
host_process_path_argument, normalize_host_input_path_text, render_host_visible_path,
};
use crate::skill::dependencies::{
NodeRuntimeDependencySpec, NodeRuntimePackageManager, PythonRuntimeDependencySpec,
PythonRuntimePackageManager,
};
pub const MANAGED_RUNTIME_ENV_MARKER_SCHEMA_VERSION: u32 = 2;
pub const WINDOWS_ARM_PERSISTENT_SESSION_UNSUPPORTED_REASON: &str = "windows_arm_is_not_supported";
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ManagedRuntimeRootSource {
HostConfigured,
RuntimeRootDefault,
}
impl ManagedRuntimeRootSource {
pub fn as_str(self) -> &'static str {
match self {
Self::HostConfigured => "host_configured",
Self::RuntimeRootDefault => "runtime_root_default",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ManagedRuntimeRoots {
runtime_root: PathBuf,
distribution_root: PathBuf,
environment_root: PathBuf,
runtime_root_identity: ManagedFilesystemObjectIdentity,
distribution_root_identity: OnceLock<ManagedFilesystemObjectIdentity>,
environment_root_identity: Option<ManagedFilesystemObjectIdentity>,
distribution_source: ManagedRuntimeRootSource,
environment_source: ManagedRuntimeRootSource,
}
impl ManagedRuntimeRoots {
pub fn new(
runtime_root: &Path,
distribution_root: Option<&Path>,
environment_root: Option<&Path>,
) -> Result<Self, String> {
let canonical_runtime_root =
canonicalize_managed_runtime_directory(runtime_root, "runtime_root", false)?;
let distribution_source = if distribution_root.is_some() {
ManagedRuntimeRootSource::HostConfigured
} else {
ManagedRuntimeRootSource::RuntimeRootDefault
};
let distribution_candidate = distribution_root
.map(Path::to_path_buf)
.unwrap_or_else(|| canonical_runtime_root.join("dependencies").join("runtimes"));
if !distribution_candidate.is_absolute() {
return Err(format!(
"managed runtime distribution source={}: managed_runtime_distribution_root must be an absolute path",
distribution_source.as_str()
));
}
let (canonical_distribution_root, distribution_root_identity) = match fs::canonicalize(
&distribution_candidate,
) {
Ok(path) => {
ensure_managed_runtime_directory(&path, "managed runtime distribution root")
.map_err(|error| {
format!(
"managed runtime distribution source={}: {error}",
distribution_source.as_str()
)
})?;
let identity =
capture_managed_directory_identity(&path, "managed runtime distribution root")
.map_err(|error| {
format!(
"managed runtime distribution source={}: {error}",
distribution_source.as_str()
)
})?;
(path, Some(identity))
}
Err(error)
if distribution_source == ManagedRuntimeRootSource::RuntimeRootDefault
&& error.kind() == std::io::ErrorKind::NotFound =>
{
(distribution_candidate, None)
}
Err(error) => {
return Err(format!(
"managed runtime distribution source={}: failed to canonicalize managed runtime distribution root {}: {error}",
distribution_source.as_str(),
render_host_visible_path(&distribution_candidate)
));
}
};
let environment_source = if environment_root.is_some() {
ManagedRuntimeRootSource::HostConfigured
} else {
ManagedRuntimeRootSource::RuntimeRootDefault
};
let environment_candidate = environment_root
.map(Path::to_path_buf)
.unwrap_or_else(|| canonical_runtime_root.join("dependencies").join("envs"));
if !environment_candidate.is_absolute() {
return Err(format!(
"managed runtime environment source={}: managed_runtime_environment_root must be an absolute path",
environment_source.as_str()
));
}
let platform_capability = current_managed_runtime_persistent_session_capability();
let (canonical_environment_root, environment_root_identity) = if platform_capability
.supported
{
let canonical = canonicalize_managed_runtime_directory(
&environment_candidate,
"managed runtime environment root",
true,
)
.map_err(|error| {
format!(
"managed runtime environment source={}: {error}",
environment_source.as_str()
)
})?;
let identity =
capture_managed_directory_identity(&canonical, "managed runtime environment root")
.map_err(|error| {
format!(
"managed runtime environment source={}: {error}",
environment_source.as_str()
)
})?;
(canonical, Some(identity))
} else {
match fs::canonicalize(&environment_candidate) {
Ok(canonical) => {
ensure_managed_runtime_directory(
&canonical,
"managed runtime environment root",
)
.map_err(|error| {
format!(
"managed runtime environment source={}: {error}",
environment_source.as_str()
)
})?;
let identity = capture_managed_directory_identity(
&canonical,
"managed runtime environment root",
)
.map_err(|error| {
format!(
"managed runtime environment source={}: {error}",
environment_source.as_str()
)
})?;
(canonical, Some(identity))
}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
(environment_candidate, None)
}
Err(error) => {
return Err(format!(
"managed runtime environment source={}: failed to canonicalize managed runtime environment root {}: {error}",
environment_source.as_str(),
render_host_visible_path(&environment_candidate)
));
}
}
};
let distribution_identity_cell = OnceLock::new();
if let Some(identity) = distribution_root_identity {
distribution_identity_cell.set(identity).map_err(|_| {
"managed runtime distribution root identity was set twice".to_string()
})?;
}
Ok(Self {
runtime_root_identity: capture_managed_directory_identity(
&canonical_runtime_root,
"runtime_root",
)?,
distribution_root_identity: distribution_identity_cell,
environment_root_identity,
runtime_root: canonical_runtime_root,
distribution_root: canonical_distribution_root,
environment_root: canonical_environment_root,
distribution_source,
environment_source,
})
}
pub fn runtime_root(&self) -> &Path {
&self.runtime_root
}
pub fn distribution_root(&self) -> &Path {
&self.distribution_root
}
pub fn environment_root(&self) -> &Path {
&self.environment_root
}
pub fn distribution_source(&self) -> ManagedRuntimeRootSource {
self.distribution_source
}
pub fn environment_source(&self) -> ManagedRuntimeRootSource {
self.environment_source
}
pub(crate) fn validate_live_filesystem_identity(&self) -> Result<(), String> {
validate_managed_directory_identity(
&self.runtime_root,
&self.runtime_root_identity,
"runtime_root",
)?;
if let Some(identity) = self.distribution_root_identity.get() {
validate_managed_directory_identity(
&self.distribution_root,
identity,
"managed runtime distribution root",
)
.map_err(|error| {
format!(
"managed runtime distribution source={}: {error}",
self.distribution_source.as_str()
)
})?;
} else {
let current = match fs::canonicalize(&self.distribution_root) {
Ok(current) => Some(current),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,
Err(error) => {
return Err(format!(
"managed runtime distribution source={}: failed to resolve managed runtime distribution root {}: {error}",
self.distribution_source.as_str(),
render_host_visible_path(&self.distribution_root)
));
}
};
if let Some(current) = current {
if current != self.distribution_root {
return Err(format!(
"managed runtime distribution source={}: managed runtime distribution root changed identity: expected {}, got {}",
self.distribution_source.as_str(),
render_host_visible_path(&self.distribution_root),
render_host_visible_path(¤t)
));
}
ensure_managed_runtime_directory(¤t, "managed runtime distribution root")
.map_err(|error| {
format!(
"managed runtime distribution source={}: {error}",
self.distribution_source.as_str()
)
})?;
let first_identity = capture_managed_directory_identity(
¤t,
"managed runtime distribution root",
)
.map_err(|error| {
format!(
"managed runtime distribution source={}: {error}",
self.distribution_source.as_str()
)
})?;
let _ = self.distribution_root_identity.set(first_identity);
let expected_identity = self.distribution_root_identity.get().ok_or_else(|| {
format!(
"managed runtime distribution source={}: managed runtime distribution root identity is unavailable",
self.distribution_source.as_str()
)
})?;
validate_managed_directory_identity(
&self.distribution_root,
expected_identity,
"managed runtime distribution root",
)
.map_err(|error| {
format!(
"managed runtime distribution source={}: {error}",
self.distribution_source.as_str()
)
})?;
}
}
match self.environment_root_identity.as_ref() {
Some(identity) => validate_managed_directory_identity(
&self.environment_root,
identity,
"managed runtime environment root",
)
.map_err(|error| {
format!(
"managed runtime environment source={}: {error}",
self.environment_source.as_str()
)
}),
None if !current_managed_runtime_persistent_session_capability().supported => Ok(()),
None => Err(format!(
"managed runtime environment source={}: managed runtime environment root identity is unavailable",
self.environment_source.as_str()
)),
}
}
}
fn canonicalize_managed_runtime_directory(
path: &Path,
label: &str,
create: bool,
) -> Result<PathBuf, String> {
if !path.is_absolute() {
return Err(format!("{label} must be an absolute path"));
}
if create {
fs::create_dir_all(path).map_err(|error| {
format!(
"failed to create {label} {}: {error}",
render_host_visible_path(path)
)
})?;
}
let canonical = fs::canonicalize(path).map_err(|error| {
format!(
"failed to canonicalize {label} {}: {error}",
render_host_visible_path(path)
)
})?;
normalize_host_input_path_text(&canonical.to_string_lossy())
.map_err(|error| format!("{label}: {error}"))?;
ensure_managed_runtime_directory(&canonical, label)?;
Ok(canonical)
}
fn ensure_managed_runtime_directory(path: &Path, label: &str) -> Result<(), String> {
let metadata = fs::metadata(path).map_err(|error| {
format!(
"failed to inspect {label} {}: {error}",
render_host_visible_path(path)
)
})?;
if !metadata.is_dir() {
return Err(format!(
"{label} is not a directory: {}",
render_host_visible_path(path)
));
}
Ok(())
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct ManagedRuntimePersistentSessionCapability {
pub supported: bool,
pub target_os: String,
pub target_arch: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
pub fn current_managed_runtime_persistent_session_capability()
-> ManagedRuntimePersistentSessionCapability {
let windows_arm = cfg!(all(windows, target_arch = "aarch64"));
let supported_family = cfg!(any(target_os = "linux", target_os = "macos"))
|| cfg!(all(windows, not(target_arch = "aarch64")));
ManagedRuntimePersistentSessionCapability {
supported: supported_family && !windows_arm,
target_os: env::consts::OS.to_string(),
target_arch: env::consts::ARCH.to_string(),
reason: windows_arm.then(|| WINDOWS_ARM_PERSISTENT_SESSION_UNSUPPORTED_REASON.to_string()),
}
}
#[cfg(test)]
mod persistent_session_capability_tests {
use super::current_managed_runtime_persistent_session_capability;
#[cfg(any(
target_os = "linux",
target_os = "macos",
all(windows, not(target_arch = "aarch64"))
))]
#[test]
fn supported_target_reports_persistent_sessions() {
let capability = current_managed_runtime_persistent_session_capability();
assert!(capability.supported);
assert_eq!(capability.target_os, std::env::consts::OS);
assert_eq!(capability.target_arch, std::env::consts::ARCH);
assert_eq!(capability.reason, None);
}
#[cfg(all(windows, target_arch = "aarch64"))]
#[test]
fn windows_arm_reports_stable_unsupported_reason() {
let capability = current_managed_runtime_persistent_session_capability();
assert!(!capability.supported);
assert_eq!(capability.target_os, "windows");
assert_eq!(capability.target_arch, "aarch64");
assert_eq!(
capability.reason.as_deref(),
Some(super::WINDOWS_ARM_PERSISTENT_SESSION_UNSUPPORTED_REASON)
);
}
}
static NEXT_MANAGED_RUNTIME_BUILD_SEQUENCE: AtomicU64 = AtomicU64::new(0);
const MAX_MANAGED_RUNTIME_BUILD_DIR_ATTEMPTS: usize = 64;
const MAX_MANAGED_RUNTIME_BACKUP_RECOVERY_RECORDS: usize = 1_024;
const MANAGED_RUNTIME_BACKUP_RECOVERY_RETRY_DELAY: Duration = Duration::from_millis(100);
const MIN_MANAGED_NODE_RUNTIME_VERSION: &str = "22.0.0";
struct ManagedRuntimeBackupRecoveryCenter {
records: Mutex<VecDeque<ManagedRuntimeBackupRecoveryRecord>>,
changed: Condvar,
reserved: Arc<AtomicUsize>,
}
struct ManagedRuntimeBackupRecoveryPermit {
reserved: Arc<AtomicUsize>,
}
impl Drop for ManagedRuntimeBackupRecoveryPermit {
fn drop(&mut self) {
self.reserved.fetch_sub(1, Ordering::AcqRel);
}
}
enum ManagedRuntimeBackupRecoveryAction {
RemoveCommittedBackup,
RestoreFailedPublication {
plan: Box<ManagedRuntimeEnvPlan>,
},
}
struct ManagedRuntimeBackupRecoveryRecord {
backup_path: PathBuf,
identity: ManagedFilesystemObjectIdentity,
action: ManagedRuntimeBackupRecoveryAction,
_permit: ManagedRuntimeBackupRecoveryPermit,
error_reported: bool,
}
struct ManagedRuntimeEnvFileLock {
file: File,
path: PathBuf,
}
impl Drop for ManagedRuntimeEnvFileLock {
fn drop(&mut self) {
if let Err(error) = unlock_managed_runtime_env_file(&self.file) {
crate::runtime_logging::warn(format!(
"[LuaSkill:warn] failed to unlock managed runtime environment {}: {error}",
render_host_visible_path(&self.path)
));
}
}
}
#[derive(Debug)]
pub(crate) struct ManagedRuntimeEnvLease {
_guard: ManagedRuntimeEnvLeaseFileLock,
}
#[derive(Debug)]
struct ManagedRuntimeEnvLeaseFileLock {
file: File,
path: PathBuf,
}
impl Drop for ManagedRuntimeEnvLeaseFileLock {
fn drop(&mut self) {
if let Err(error) = unlock_managed_runtime_env_lease_file(&self.file) {
crate::runtime_logging::warn(format!(
"[LuaSkill:warn] failed to unlock managed runtime environment lifecycle lease {}: {error}",
render_host_visible_path(&self.path)
));
}
}
}
fn managed_runtime_env_lock_registry() -> &'static Mutex<HashMap<PathBuf, Weak<Mutex<()>>>> {
static REGISTRY: OnceLock<Mutex<HashMap<PathBuf, Weak<Mutex<()>>>>> = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
}
fn managed_runtime_env_lock(env_dir: &Path) -> Arc<Mutex<()>> {
let mut registry = managed_runtime_env_lock_registry()
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
registry.retain(|_, lock| lock.strong_count() > 0);
if let Some(lock) = registry.get(env_dir).and_then(Weak::upgrade) {
return lock;
}
let lock = Arc::new(Mutex::new(()));
registry.insert(env_dir.to_path_buf(), Arc::downgrade(&lock));
lock
}
fn managed_runtime_backup_recovery_center()
-> Result<Arc<ManagedRuntimeBackupRecoveryCenter>, String> {
static CENTER: OnceLock<Result<Arc<ManagedRuntimeBackupRecoveryCenter>, String>> =
OnceLock::new();
match CENTER.get_or_init(initialize_managed_runtime_backup_recovery_center) {
Ok(center) => Ok(Arc::clone(center)),
Err(error) => Err(error.clone()),
}
}
fn initialize_managed_runtime_backup_recovery_center()
-> Result<Arc<ManagedRuntimeBackupRecoveryCenter>, String> {
let reserved = Arc::new(AtomicUsize::new(0));
let center = Arc::new(ManagedRuntimeBackupRecoveryCenter {
records: Mutex::new(VecDeque::new()),
changed: Condvar::new(),
reserved,
});
let worker_center = Arc::clone(¢er);
thread::Builder::new()
.name("luaskills-managed-env-backup-recovery".to_string())
.spawn(move || run_managed_runtime_backup_recovery_worker(worker_center))
.map_err(|error| {
format!("failed to start managed environment backup recovery worker: {error}")
})?;
Ok(center)
}
fn reserve_managed_runtime_backup_recovery_slot() -> Result<
(
Arc<ManagedRuntimeBackupRecoveryCenter>,
ManagedRuntimeBackupRecoveryPermit,
),
String,
> {
let center = managed_runtime_backup_recovery_center()?;
center
.reserved
.fetch_update(Ordering::AcqRel, Ordering::Acquire, |reserved| {
(reserved < MAX_MANAGED_RUNTIME_BACKUP_RECOVERY_RECORDS).then_some(reserved + 1)
})
.map_err(|reserved| {
format!(
"managed environment backup recovery capacity is exhausted; reserved={reserved} max={MAX_MANAGED_RUNTIME_BACKUP_RECOVERY_RECORDS}"
)
})?;
let permit = ManagedRuntimeBackupRecoveryPermit {
reserved: Arc::clone(¢er.reserved),
};
Ok((center, permit))
}
impl ManagedRuntimeBackupRecoveryCenter {
fn enqueue(&self, record: ManagedRuntimeBackupRecoveryRecord) {
let mut records = self
.records
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
records.push_back(record);
self.changed.notify_one();
}
fn wait_next(&self) -> ManagedRuntimeBackupRecoveryRecord {
let mut records = self
.records
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
while records.is_empty() {
records = self
.changed
.wait(records)
.unwrap_or_else(std::sync::PoisonError::into_inner);
}
records
.pop_front()
.expect("non-empty managed environment recovery queue must yield one record")
}
}
fn run_managed_runtime_backup_recovery_worker(center: Arc<ManagedRuntimeBackupRecoveryCenter>) {
loop {
let mut record = center.wait_next();
let recovery = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
recover_managed_runtime_backup(&mut record)
}));
match recovery {
Ok(Ok(())) => {
drop(record);
}
Ok(Err(error)) => {
if !record.error_reported {
crate::runtime_logging::warn(format!(
"[LuaSkill:warn] managed environment backup recovery remains pending for {}: {error}",
render_host_visible_path(&record.backup_path)
));
record.error_reported = true;
}
thread::sleep(MANAGED_RUNTIME_BACKUP_RECOVERY_RETRY_DELAY);
center.enqueue(record);
}
Err(_) => {
if !record.error_reported {
crate::runtime_logging::warn(format!(
"[LuaSkill:warn] managed environment backup recovery panicked and remains pending for {}",
render_host_visible_path(&record.backup_path)
));
record.error_reported = true;
}
thread::sleep(MANAGED_RUNTIME_BACKUP_RECOVERY_RETRY_DELAY);
center.enqueue(record);
}
}
}
}
fn recover_managed_runtime_backup(
record: &mut ManagedRuntimeBackupRecoveryRecord,
) -> Result<(), String> {
match &record.action {
ManagedRuntimeBackupRecoveryAction::RemoveCommittedBackup => {
remove_identity_bound_managed_runtime_backup(&record.backup_path, &record.identity)
}
ManagedRuntimeBackupRecoveryAction::RestoreFailedPublication { plan } => {
recover_failed_managed_runtime_publication(plan, &record.backup_path, &record.identity)
}
}
}
fn remove_identity_bound_managed_runtime_backup(
backup_path: &Path,
identity: &ManagedFilesystemObjectIdentity,
) -> Result<(), String> {
match fs::symlink_metadata(backup_path) {
Ok(_) => {}
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
Err(error) => {
return Err(format!(
"failed to inspect replaced managed environment {}: {error}",
render_managed_runtime_path(backup_path)
));
}
}
validate_managed_directory_identity(backup_path, identity, "replaced managed environment")?;
match fs::remove_dir_all(backup_path) {
Ok(()) => Ok(()),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(error) => Err(format!(
"failed to remove replaced managed environment {}: {error}",
render_managed_runtime_path(backup_path)
)),
}
}
fn recover_failed_managed_runtime_publication(
plan: &ManagedRuntimeEnvPlan,
backup_path: &Path,
identity: &ManagedFilesystemObjectIdentity,
) -> Result<(), String> {
let build_lock = managed_runtime_env_lock(&plan.env_dir);
let _build_guard = build_lock
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let _file_lock = acquire_managed_runtime_env_file_lock(plan)?;
if managed_env_is_ready(plan)? {
return remove_identity_bound_managed_runtime_backup(backup_path, identity);
}
match fs::symlink_metadata(&plan.env_dir) {
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
Ok(_) => {
return Err(format!(
"cannot restore replaced managed environment while target exists: {}",
render_managed_runtime_path(&plan.env_dir)
));
}
Err(error) => {
return Err(format!(
"failed to inspect managed environment recovery target {}: {error}",
render_managed_runtime_path(&plan.env_dir)
));
}
}
let _publish_lease = try_acquire_managed_runtime_env_publish_lease(plan)?;
validate_managed_directory_identity(backup_path, identity, "replaced managed environment")?;
fs::rename(backup_path, &plan.env_dir).map_err(|error| {
format!(
"failed to restore replaced managed environment {} as {}: {error}",
render_managed_runtime_path(backup_path),
render_managed_runtime_path(&plan.env_dir)
)
})
}
fn acquire_managed_runtime_env_file_lock(
plan: &ManagedRuntimeEnvPlan,
) -> Result<ManagedRuntimeEnvFileLock, String> {
let parent = plan.env_dir.parent().ok_or_else(|| {
format!(
"managed env directory has no parent: {}",
render_managed_runtime_path(&plan.env_dir)
)
})?;
fs::create_dir_all(parent).map_err(|error| {
format!(
"Failed to create managed environment parent {}: {error}",
render_managed_runtime_path(parent)
)
})?;
let path = parent.join(format!(".luaskills-env-{}.lock", plan.env_hash));
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&path)
.map_err(|error| {
format!(
"Failed to open managed environment lock {}: {error}",
render_managed_runtime_path(&path)
)
})?;
lock_managed_runtime_env_file(&file).map_err(|error| {
format!(
"Failed to lock managed environment {}: {error}",
render_managed_runtime_path(&path)
)
})?;
Ok(ManagedRuntimeEnvFileLock { file, path })
}
pub(crate) fn acquire_ready_managed_env_lease(
plan: &ManagedRuntimeEnvPlan,
) -> Result<ManagedRuntimeEnvLease, String> {
ensure_managed_env(plan)?;
let guard = acquire_managed_runtime_env_shared_lease(plan)?;
if !managed_env_is_ready(plan)? {
return Err(format!(
"managed runtime environment changed before lifecycle lease acquisition: {}",
render_managed_runtime_path(&plan.env_dir)
));
}
Ok(ManagedRuntimeEnvLease { _guard: guard })
}
fn open_managed_runtime_env_lease_file(
plan: &ManagedRuntimeEnvPlan,
) -> Result<(File, PathBuf), String> {
let parent = plan.env_dir.parent().ok_or_else(|| {
format!(
"managed env directory has no parent: {}",
render_managed_runtime_path(&plan.env_dir)
)
})?;
fs::create_dir_all(parent).map_err(|error| {
format!(
"Failed to create managed environment parent {}: {error}",
render_managed_runtime_path(parent)
)
})?;
let path = parent.join(format!(".luaskills-env-{}.lease", plan.env_hash));
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&path)
.map_err(|error| {
format!(
"Failed to open managed environment lifecycle lease {}: {error}",
render_managed_runtime_path(&path)
)
})?;
Ok((file, path))
}
fn acquire_managed_runtime_env_shared_lease(
plan: &ManagedRuntimeEnvPlan,
) -> Result<ManagedRuntimeEnvLeaseFileLock, String> {
let (file, path) = open_managed_runtime_env_lease_file(plan)?;
lock_managed_runtime_env_lease_shared(&file).map_err(|error| {
format!(
"Failed to acquire shared managed environment lifecycle lease {}: {error}",
render_managed_runtime_path(&path)
)
})?;
Ok(ManagedRuntimeEnvLeaseFileLock { file, path })
}
fn try_acquire_managed_runtime_env_publish_lease(
plan: &ManagedRuntimeEnvPlan,
) -> Result<ManagedRuntimeEnvLeaseFileLock, String> {
let (file, path) = open_managed_runtime_env_lease_file(plan)?;
match try_lock_managed_runtime_env_lease_exclusive(&file) {
Ok(true) => Ok(ManagedRuntimeEnvLeaseFileLock { file, path }),
Ok(false) => Err(format!(
"managed runtime environment is busy with active lifecycle leases: {}",
render_managed_runtime_path(&plan.env_dir)
)),
Err(error) => Err(format!(
"Failed to acquire exclusive managed environment lifecycle lease {}: {error}",
render_managed_runtime_path(&path)
)),
}
}
#[cfg(unix)]
fn lock_managed_runtime_env_file(file: &File) -> Result<(), std::io::Error> {
use std::os::fd::AsRawFd;
loop {
let status = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX) };
if status == 0 {
return Ok(());
}
let error = std::io::Error::last_os_error();
if error.kind() != std::io::ErrorKind::Interrupted {
return Err(error);
}
}
}
#[cfg(unix)]
fn unlock_managed_runtime_env_file(file: &File) -> Result<(), std::io::Error> {
use std::os::fd::AsRawFd;
let status = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) };
if status == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
#[cfg(windows)]
fn lock_managed_runtime_env_file(file: &File) -> Result<(), std::io::Error> {
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Storage::FileSystem::{LOCKFILE_EXCLUSIVE_LOCK, LockFileEx};
use windows_sys::Win32::System::IO::OVERLAPPED;
let mut overlapped = unsafe { std::mem::zeroed::<OVERLAPPED>() };
let status = unsafe {
LockFileEx(
file.as_raw_handle() as _,
LOCKFILE_EXCLUSIVE_LOCK,
0,
u32::MAX,
u32::MAX,
&mut overlapped,
)
};
if status != 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
#[cfg(windows)]
fn unlock_managed_runtime_env_file(file: &File) -> Result<(), std::io::Error> {
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Storage::FileSystem::UnlockFileEx;
use windows_sys::Win32::System::IO::OVERLAPPED;
let mut overlapped = unsafe { std::mem::zeroed::<OVERLAPPED>() };
let status = unsafe {
UnlockFileEx(
file.as_raw_handle() as _,
0,
u32::MAX,
u32::MAX,
&mut overlapped,
)
};
if status != 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
#[cfg(not(any(unix, windows)))]
fn lock_managed_runtime_env_file(_file: &File) -> Result<(), std::io::Error> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"managed runtime cross-process file locking is unsupported on this platform",
))
}
#[cfg(not(any(unix, windows)))]
fn unlock_managed_runtime_env_file(_file: &File) -> Result<(), std::io::Error> {
Ok(())
}
#[cfg(unix)]
fn lock_managed_runtime_env_lease_shared(file: &File) -> Result<(), std::io::Error> {
use std::os::fd::AsRawFd;
loop {
let status = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_SH) };
if status == 0 {
return Ok(());
}
let error = std::io::Error::last_os_error();
if error.kind() != std::io::ErrorKind::Interrupted {
return Err(error);
}
}
}
#[cfg(unix)]
fn try_lock_managed_runtime_env_lease_exclusive(file: &File) -> Result<bool, std::io::Error> {
use std::os::fd::AsRawFd;
loop {
let status = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
if status == 0 {
return Ok(true);
}
let error = std::io::Error::last_os_error();
match error.kind() {
std::io::ErrorKind::WouldBlock => return Ok(false),
std::io::ErrorKind::Interrupted => continue,
_ => return Err(error),
}
}
}
#[cfg(unix)]
fn unlock_managed_runtime_env_lease_file(file: &File) -> Result<(), std::io::Error> {
use std::os::fd::AsRawFd;
let status = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_UN) };
if status == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
#[cfg(windows)]
fn lock_managed_runtime_env_lease_shared(file: &File) -> Result<(), std::io::Error> {
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Storage::FileSystem::LockFileEx;
use windows_sys::Win32::System::IO::OVERLAPPED;
let mut overlapped = unsafe { std::mem::zeroed::<OVERLAPPED>() };
let status = unsafe {
LockFileEx(
file.as_raw_handle() as _,
0,
0,
u32::MAX,
u32::MAX,
&mut overlapped,
)
};
if status != 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
#[cfg(windows)]
fn try_lock_managed_runtime_env_lease_exclusive(file: &File) -> Result<bool, std::io::Error> {
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Foundation::ERROR_LOCK_VIOLATION;
use windows_sys::Win32::Storage::FileSystem::{
LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx,
};
use windows_sys::Win32::System::IO::OVERLAPPED;
let mut overlapped = unsafe { std::mem::zeroed::<OVERLAPPED>() };
let status = unsafe {
LockFileEx(
file.as_raw_handle() as _,
LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
0,
u32::MAX,
u32::MAX,
&mut overlapped,
)
};
if status != 0 {
return Ok(true);
}
let error = std::io::Error::last_os_error();
if error.raw_os_error() == Some(ERROR_LOCK_VIOLATION as i32) {
Ok(false)
} else {
Err(error)
}
}
#[cfg(windows)]
fn unlock_managed_runtime_env_lease_file(file: &File) -> Result<(), std::io::Error> {
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Storage::FileSystem::UnlockFileEx;
use windows_sys::Win32::System::IO::OVERLAPPED;
let mut overlapped = unsafe { std::mem::zeroed::<OVERLAPPED>() };
let status = unsafe {
UnlockFileEx(
file.as_raw_handle() as _,
0,
u32::MAX,
u32::MAX,
&mut overlapped,
)
};
if status != 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
#[cfg(not(any(unix, windows)))]
fn lock_managed_runtime_env_lease_shared(_file: &File) -> Result<(), std::io::Error> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"managed runtime environment lifecycle leasing is unsupported on this platform",
))
}
#[cfg(not(any(unix, windows)))]
fn try_lock_managed_runtime_env_lease_exclusive(_file: &File) -> Result<bool, std::io::Error> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"managed runtime environment lifecycle leasing is unsupported on this platform",
))
}
#[cfg(not(any(unix, windows)))]
fn unlock_managed_runtime_env_lease_file(_file: &File) -> Result<(), std::io::Error> {
Ok(())
}
fn allocate_managed_runtime_build_sequence() -> Result<u64, String> {
NEXT_MANAGED_RUNTIME_BUILD_SEQUENCE
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
current.checked_add(1)
})
.map(|previous| previous + 1)
.map_err(|_| "managed runtime build sequence is exhausted".to_string())
}
fn render_managed_runtime_path(path: &Path) -> String {
render_host_visible_path(path)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ManagedRuntimeKind {
Python,
Node,
}
impl ManagedRuntimeKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Python => "python",
Self::Node => "node",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ManagedRuntimeEnvHashInput {
pub runtime: ManagedRuntimeKind,
pub runtime_version: String,
pub platform: String,
pub package_manager: String,
pub package_manager_version: String,
pub lock_hash: String,
pub package_manifest_hash: Option<String>,
pub runtime_install_manifest_hash: String,
pub runtime_executable_hash: String,
pub package_manager_install_manifest_hash: String,
pub package_manager_executable_hash: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ManagedRuntimeEnvMarker {
pub schema_version: u32,
pub runtime: String,
pub runtime_version: String,
pub package_manager: String,
pub package_manager_version: String,
pub platform: String,
pub lock_hash: String,
pub package_manifest_hash: Option<String>,
pub runtime_install_manifest_hash: String,
pub runtime_executable_hash: String,
pub package_manager_install_manifest_hash: String,
pub package_manager_executable_hash: String,
pub env_hash: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ManagedRuntimeInstallManifest {
pub schema_version: u32,
pub runtime: String,
pub version: String,
pub platform: String,
pub executable: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ManagedRuntimeInstallDescriptor {
pub runtime: ManagedRuntimeKind,
pub version: String,
pub platform: String,
pub install_root: PathBuf,
pub executable: PathBuf,
pub manifest_hash: String,
pub executable_hash: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ManagedRuntimeInstallIdentity {
install_root: PathBuf,
executable: PathBuf,
manifest_hash: String,
executable_hash: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ManagedRuntimeEnvPlan {
pub runtime: ManagedRuntimeKind,
pub platform: String,
pub runtime_root: PathBuf,
pub distribution_root: PathBuf,
pub environment_root: PathBuf,
pub distribution_source: ManagedRuntimeRootSource,
pub environment_source: ManagedRuntimeRootSource,
pub runtime_install_root: PathBuf,
pub runtime_version: String,
pub runtime_executable: PathBuf,
pub runtime_install_manifest_hash: String,
pub runtime_executable_hash: String,
pub package_manager: String,
pub package_manager_version: String,
pub package_manager_executable: PathBuf,
pub package_manager_install_root: PathBuf,
pub package_manager_install_manifest_hash: String,
pub package_manager_executable_hash: String,
pub(crate) managed_runtime_roots: Arc<ManagedRuntimeRoots>,
pub package_manifest_path: Option<PathBuf>,
pub lockfile_path: PathBuf,
pub lock_hash: String,
pub package_manifest_hash: Option<String>,
pub env_hash: String,
pub env_dir: PathBuf,
pub expected_marker: ManagedRuntimeEnvMarker,
}
impl ManagedRuntimeEnvMarker {
pub fn expected(input: &ManagedRuntimeEnvHashInput, env_hash: String) -> Self {
Self {
schema_version: MANAGED_RUNTIME_ENV_MARKER_SCHEMA_VERSION,
runtime: input.runtime.as_str().to_string(),
runtime_version: input.runtime_version.clone(),
package_manager: input.package_manager.clone(),
package_manager_version: input.package_manager_version.clone(),
platform: input.platform.clone(),
lock_hash: input.lock_hash.clone(),
package_manifest_hash: input.package_manifest_hash.clone(),
runtime_install_manifest_hash: input.runtime_install_manifest_hash.clone(),
runtime_executable_hash: input.runtime_executable_hash.clone(),
package_manager_install_manifest_hash: input
.package_manager_install_manifest_hash
.clone(),
package_manager_executable_hash: input.package_manager_executable_hash.clone(),
env_hash,
}
}
}
impl PythonRuntimePackageManager {
fn as_managed_runtime_str(self) -> &'static str {
match self {
Self::Uv => "uv",
}
}
}
impl NodeRuntimePackageManager {
fn as_managed_runtime_str(self) -> &'static str {
match self {
Self::Pnpm => "pnpm",
}
}
}
pub fn current_managed_runtime_platform_key() -> Result<String, String> {
let arch_key = match std::env::consts::ARCH {
"x86_64" => "x64",
"aarch64" => "arm64",
other => {
return Err(format!(
"unsupported managed runtime architecture: {}",
other
));
}
};
let os_key = match std::env::consts::OS {
"windows" => {
if arch_key != "x64" {
return Err("managed runtime assets currently support Windows x64 only".to_string());
}
"windows"
}
"linux" => "linux",
"macos" => "macos",
other => {
return Err(format!(
"unsupported managed runtime operating system: {}",
other
));
}
};
Ok(format!("{}-{}", os_key, arch_key))
}
pub fn resolve_managed_runtime_install(
distribution_root: &Path,
runtime: ManagedRuntimeKind,
version: &str,
platform: &str,
) -> Result<ManagedRuntimeInstallDescriptor, String> {
validate_managed_runtime_resolver_version(version)?;
validate_managed_runtime_resolver_platform(platform)?;
let canonical_distribution_root = canonicalize_managed_runtime_directory(
distribution_root,
"managed runtime distribution root",
false,
)?;
let (family, install_name, expected_runtime) = match runtime {
ManagedRuntimeKind::Python => ("python", format!("cpython-{version}-{platform}"), "python"),
ManagedRuntimeKind::Node => ("node", format!("node-{version}-{platform}"), "node"),
};
let identity = resolve_managed_runtime_install_identity(
&canonical_distribution_root,
&runtime_install_dir(&canonical_distribution_root, family, &install_name),
expected_runtime,
version,
platform,
)?;
Ok(ManagedRuntimeInstallDescriptor {
runtime,
version: version.to_string(),
platform: platform.to_string(),
install_root: identity.install_root,
executable: identity.executable,
manifest_hash: identity.manifest_hash,
executable_hash: identity.executable_hash,
})
}
fn validate_managed_runtime_resolver_version(version: &str) -> Result<(), String> {
if version.is_empty() || version.trim() != version {
return Err(
"managed runtime version must be a non-empty exact semantic version".to_string(),
);
}
Version::parse(version)
.map(|_| ())
.map_err(|error| format!("managed runtime version '{version}' is invalid: {error}"))
}
fn validate_managed_runtime_resolver_platform(platform: &str) -> Result<(), String> {
match platform {
"windows-x64" | "linux-x64" | "linux-arm64" | "macos-x64" | "macos-arm64" => Ok(()),
"windows-arm64" | "windows-aarch64" | "windows-arm64ec" => {
Err(WINDOWS_ARM_PERSISTENT_SESSION_UNSUPPORTED_REASON.to_string())
}
_ => Err(format!(
"managed_runtime_platform_is_not_supported: {platform}"
)),
}
}
pub fn sha256_hex(bytes: &[u8]) -> String {
let digest = Sha256::digest(bytes);
format!("{:x}", digest)
}
pub fn sha256_file(path: &Path) -> Result<String, String> {
let bytes = fs::read(path).map_err(|error| {
format!(
"Failed to read {}: {}",
render_managed_runtime_path(path),
error
)
})?;
Ok(sha256_hex(&bytes))
}
fn verify_managed_runtime_build_input_hash(
path: &Path,
expected_hash: &str,
label: &str,
) -> Result<(), String> {
let actual_hash = sha256_file(path)?;
if actual_hash != expected_hash {
return Err(format!(
"managed runtime {label} changed after environment planning: expected {expected_hash}, got {actual_hash}"
));
}
Ok(())
}
pub fn compute_managed_runtime_env_hash(input: &ManagedRuntimeEnvHashInput) -> String {
let mut hasher = Sha256::new();
hash_field(
&mut hasher,
"schema_version",
MANAGED_RUNTIME_ENV_MARKER_SCHEMA_VERSION.to_string(),
);
hash_field(&mut hasher, "runtime", input.runtime.as_str());
hash_field(&mut hasher, "runtime_version", &input.runtime_version);
hash_field(&mut hasher, "platform", &input.platform);
hash_field(&mut hasher, "package_manager", &input.package_manager);
hash_field(
&mut hasher,
"package_manager_version",
&input.package_manager_version,
);
hash_field(&mut hasher, "lock_hash", &input.lock_hash);
hash_field(
&mut hasher,
"package_manifest_hash",
input.package_manifest_hash.as_deref().unwrap_or(""),
);
hash_field(
&mut hasher,
"runtime_install_manifest_hash",
&input.runtime_install_manifest_hash,
);
hash_field(
&mut hasher,
"runtime_executable_hash",
&input.runtime_executable_hash,
);
hash_field(
&mut hasher,
"package_manager_install_manifest_hash",
&input.package_manager_install_manifest_hash,
);
hash_field(
&mut hasher,
"package_manager_executable_hash",
&input.package_manager_executable_hash,
);
format!("{:x}", hasher.finalize())
}
pub fn managed_env_dir(
environment_root: &Path,
runtime: ManagedRuntimeKind,
runtime_version: &str,
env_hash: &str,
) -> PathBuf {
environment_root
.join(runtime.as_str())
.join(format!("{}-{}", runtime_prefix(runtime), runtime_version))
.join(env_hash)
}
pub fn managed_env_marker_path(env_dir: &Path) -> PathBuf {
env_dir.join(".luaskills-env.json")
}
fn managed_env_marker_path_is_file(marker_path: &Path) -> Result<bool, String> {
match fs::symlink_metadata(marker_path) {
Ok(metadata) if metadata.file_type().is_symlink() => Err(format!(
"Managed runtime env marker must not be a symbolic link: {}",
render_managed_runtime_path(marker_path)
)),
Ok(metadata) if metadata.is_file() => Ok(true),
Ok(_) => Err(format!(
"Managed runtime env marker is not a file: {}",
render_managed_runtime_path(marker_path)
)),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(error) => Err(format!(
"Failed to inspect managed runtime env marker {}: {}",
render_managed_runtime_path(marker_path),
error
)),
}
}
fn managed_runtime_directory_path_is_directory(
path: &Path,
directory_label: &str,
) -> Result<bool, String> {
match fs::symlink_metadata(path) {
Ok(metadata) if metadata.file_type().is_symlink() => Err(format!(
"{} must not be a symbolic link: {}",
directory_label,
render_managed_runtime_path(path)
)),
Ok(metadata) if metadata.is_dir() => Ok(true),
Ok(_) => Err(format!(
"{} is not a directory: {}",
directory_label,
render_managed_runtime_path(path)
)),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(error) => Err(format!(
"Failed to inspect {} {}: {}",
directory_label,
render_managed_runtime_path(path),
error
)),
}
}
pub fn read_managed_env_marker(path: &Path) -> Result<ManagedRuntimeEnvMarker, String> {
let text = fs::read_to_string(path).map_err(|error| {
format!(
"Failed to read {}: {}",
render_managed_runtime_path(path),
error
)
})?;
serde_json::from_str(&text).map_err(|error| {
format!(
"Failed to parse {}: {}",
render_managed_runtime_path(path),
error
)
})
}
pub fn managed_env_marker_matches(
actual: &ManagedRuntimeEnvMarker,
expected: &ManagedRuntimeEnvMarker,
) -> bool {
actual == expected
}
pub fn ensure_managed_env(plan: &ManagedRuntimeEnvPlan) -> Result<(), String> {
validate_managed_runtime_plan_assets(plan)?;
if managed_env_is_ready(plan)? {
return Ok(());
}
let build_lock = managed_runtime_env_lock(&plan.env_dir);
let _build_guard = build_lock
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let _file_lock = acquire_managed_runtime_env_file_lock(plan)?;
if managed_env_is_ready(plan)? {
return Ok(());
}
match plan.runtime {
ManagedRuntimeKind::Python => create_python_env(plan),
ManagedRuntimeKind::Node => create_node_env(plan),
}
}
pub fn managed_env_is_ready(plan: &ManagedRuntimeEnvPlan) -> Result<bool, String> {
if !managed_runtime_directory_path_is_directory(
&plan.env_dir,
"managed runtime environment directory",
)? {
return Ok(false);
}
let marker_path = managed_env_marker_path(&plan.env_dir);
if !managed_env_marker_path_is_file(&marker_path)? {
return Ok(false);
}
let actual = read_managed_env_marker(&marker_path)?;
Ok(managed_env_marker_matches(&actual, &plan.expected_marker))
}
pub(crate) fn validate_managed_runtime_plan_assets(
plan: &ManagedRuntimeEnvPlan,
) -> Result<(), String> {
plan.managed_runtime_roots
.validate_live_filesystem_identity()?;
if plan.runtime_root != plan.managed_runtime_roots.runtime_root()
|| plan.distribution_root != plan.managed_runtime_roots.distribution_root()
|| plan.environment_root != plan.managed_runtime_roots.environment_root()
|| plan.distribution_source != plan.managed_runtime_roots.distribution_source()
|| plan.environment_source != plan.managed_runtime_roots.environment_source()
{
return Err("managed runtime plan root authority changed".to_string());
}
if plan.env_dir == plan.environment_root || !plan.env_dir.starts_with(&plan.environment_root) {
return Err(format!(
"managed runtime environment source={} produced an env directory outside its root: {}",
plan.environment_source.as_str(),
render_managed_runtime_path(&plan.env_dir)
));
}
let runtime_identity = resolve_managed_runtime_install_identity(
&plan.distribution_root,
&plan.runtime_install_root,
plan.runtime.as_str(),
&plan.runtime_version,
&plan.platform,
)
.map_err(|error| {
format!(
"managed runtime distribution source={}: {error}",
plan.distribution_source.as_str()
)
})?;
validate_managed_runtime_install_identity_matches(
"runtime",
&runtime_identity,
&plan.runtime_install_root,
&plan.runtime_executable,
&plan.runtime_install_manifest_hash,
&plan.runtime_executable_hash,
)?;
let package_manager_platform = if plan.package_manager == "pnpm" {
"any"
} else {
plan.platform.as_str()
};
let package_manager_identity = resolve_managed_runtime_install_identity(
&plan.distribution_root,
&plan.package_manager_install_root,
&plan.package_manager,
&plan.package_manager_version,
package_manager_platform,
)
.map_err(|error| {
format!(
"managed runtime distribution source={}: {error}",
plan.distribution_source.as_str()
)
})?;
validate_managed_runtime_install_identity_matches(
"package manager",
&package_manager_identity,
&plan.package_manager_install_root,
&plan.package_manager_executable,
&plan.package_manager_install_manifest_hash,
&plan.package_manager_executable_hash,
)
}
fn validate_managed_runtime_install_identity_matches(
label: &str,
actual: &ManagedRuntimeInstallIdentity,
expected_install_root: &Path,
expected_executable: &Path,
expected_manifest_hash: &str,
expected_executable_hash: &str,
) -> Result<(), String> {
if actual.install_root != expected_install_root
|| actual.executable != expected_executable
|| actual.manifest_hash != expected_manifest_hash
|| actual.executable_hash != expected_executable_hash
{
return Err(format!(
"managed runtime {label} installation identity changed"
));
}
Ok(())
}
fn create_python_env(plan: &ManagedRuntimeEnvPlan) -> Result<(), String> {
validate_managed_runtime_plan_assets(plan)?;
let build_dir = prepare_build_dir(plan)?;
let build_result = (|| {
let build_lockfile = build_dir.join("requirements.lock");
fs::copy(&plan.lockfile_path, &build_lockfile).map_err(|error| {
format!(
"Failed to copy {} into {}: {}",
render_managed_runtime_path(&plan.lockfile_path),
render_managed_runtime_path(&build_lockfile),
error
)
})?;
verify_managed_runtime_build_input_hash(
&build_lockfile,
&plan.lock_hash,
"Python lockfile",
)?;
let venv_dir = build_dir.join(".venv");
let mut create_command = Command::new(&plan.package_manager_executable);
create_command
.arg("venv")
.arg("--python")
.arg(host_process_path_argument(&plan.runtime_executable))
.arg(host_process_path_argument(&venv_dir))
.current_dir(host_process_path_argument(&build_dir));
configure_managed_command_base_environment(
&mut create_command,
&build_dir,
&[
managed_executable_parent(&plan.package_manager_executable)?.to_path_buf(),
managed_executable_parent(&plan.runtime_executable)?.to_path_buf(),
],
)?;
create_command.env("UV_NO_CONFIG", "1");
run_command(
&mut create_command,
"create managed Python virtual environment",
)?;
let python_executable = python_venv_executable(&venv_dir);
let mut sync_command = Command::new(&plan.package_manager_executable);
sync_command
.arg("pip")
.arg("sync")
.arg(host_process_path_argument(&build_lockfile))
.arg("--python")
.arg(host_process_path_argument(&python_executable))
.arg("--cache-dir")
.arg(host_process_path_argument(&package_store_dir_for_plan(
plan,
)))
.current_dir(host_process_path_argument(&build_dir));
configure_managed_command_base_environment(
&mut sync_command,
&build_dir,
&[
managed_executable_parent(&plan.package_manager_executable)?.to_path_buf(),
managed_executable_parent(&python_executable)?.to_path_buf(),
managed_executable_parent(&plan.runtime_executable)?.to_path_buf(),
],
)?;
sync_command.env("UV_NO_CONFIG", "1");
run_command(&mut sync_command, "synchronize managed Python environment")?;
finish_build_dir(plan, build_dir.clone())
})();
cleanup_unpublished_build_after_result(&build_dir, build_result)
}
fn create_node_env(plan: &ManagedRuntimeEnvPlan) -> Result<(), String> {
validate_managed_runtime_plan_assets(plan)?;
let build_dir = prepare_build_dir(plan)?;
let package_json = plan.package_manifest_path.as_ref().ok_or_else(|| {
"node package_json is required to create a managed Node environment".to_string()
})?;
let build_package_json = build_dir.join("package.json");
let build_lockfile = build_dir.join("pnpm-lock.yaml");
let install_result = fs::copy(package_json, &build_package_json)
.map_err(|error| {
format!(
"Failed to copy {} into {}: {}",
render_managed_runtime_path(package_json),
render_managed_runtime_path(&build_dir),
error
)
})
.and_then(|_| {
fs::copy(&plan.lockfile_path, &build_lockfile).map_err(|error| {
format!(
"Failed to copy {} into {}: {}",
render_managed_runtime_path(&plan.lockfile_path),
render_managed_runtime_path(&build_dir),
error
)
})
})
.and_then(|_| {
let expected_package_hash = plan.package_manifest_hash.as_deref().ok_or_else(|| {
"node package_json hash is unavailable during environment creation".to_string()
})?;
verify_managed_runtime_build_input_hash(
&build_package_json,
expected_package_hash,
"Node package manifest",
)?;
verify_managed_runtime_build_input_hash(
&build_lockfile,
&plan.lock_hash,
"Node lockfile",
)
})
.and_then(|_| {
let mut install_command = Command::new(&plan.runtime_executable);
install_command
.arg(host_process_path_argument(&plan.package_manager_executable))
.arg("install")
.arg("--frozen-lockfile")
.arg("--node-linker")
.arg("hoisted")
.arg("--store-dir")
.arg(host_process_path_argument(&package_store_dir_for_plan(
plan,
)))
.current_dir(host_process_path_argument(&build_dir));
configure_managed_command_base_environment(
&mut install_command,
&build_dir,
&[
managed_executable_parent(&plan.runtime_executable)?.to_path_buf(),
managed_executable_parent(&plan.package_manager_executable)?.to_path_buf(),
build_dir.join("node_modules").join(".bin"),
],
)?;
install_command.env(
"PNPM_HOME",
host_process_path_argument(managed_executable_parent(
&plan.package_manager_executable,
)?),
);
run_command(&mut install_command, "install managed Node environment")
})
.and_then(|()| finish_build_dir(plan, build_dir.clone()));
cleanup_unpublished_build_after_result(&build_dir, install_result)
}
fn cleanup_unpublished_build_after_result(
build_dir: &Path,
result: Result<(), String>,
) -> Result<(), String> {
let Err(primary_error) = result else {
return Ok(());
};
match fs::remove_dir_all(build_dir) {
Ok(()) => Err(primary_error),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Err(primary_error),
Err(cleanup_error) => Err(format!(
"{primary_error}; failed to remove unpublished managed environment {}: {cleanup_error}",
render_managed_runtime_path(build_dir)
)),
}
}
fn prepare_build_dir(plan: &ManagedRuntimeEnvPlan) -> Result<PathBuf, String> {
prepare_build_dir_with_sequence(plan, allocate_managed_runtime_build_sequence)
}
fn prepare_build_dir_with_sequence<F>(
plan: &ManagedRuntimeEnvPlan,
mut allocate_sequence: F,
) -> Result<PathBuf, String>
where
F: FnMut() -> Result<u64, String>,
{
let parent = plan.env_dir.parent().ok_or_else(|| {
format!(
"managed env directory has no parent: {}",
render_managed_runtime_path(&plan.env_dir)
)
})?;
fs::create_dir_all(parent).map_err(|error| {
format!(
"Failed to create {}: {}",
render_managed_runtime_path(parent),
error
)
})?;
for _attempt in 0..MAX_MANAGED_RUNTIME_BUILD_DIR_ATTEMPTS {
let sequence = allocate_sequence()?;
let build_dir = parent.join(format!(
".building-{}-{}-{sequence}",
plan.env_hash,
std::process::id()
));
match fs::create_dir(&build_dir) {
Ok(()) => return Ok(build_dir),
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(error) => {
return Err(format!(
"Failed to create {}: {}",
render_managed_runtime_path(&build_dir),
error
));
}
}
}
Err(format!(
"failed to allocate managed runtime build directory after {MAX_MANAGED_RUNTIME_BUILD_DIR_ATTEMPTS} attempts"
))
}
fn finish_build_dir(plan: &ManagedRuntimeEnvPlan, build_dir: PathBuf) -> Result<(), String> {
finish_build_dir_with_backup_remover(
plan,
build_dir,
remove_identity_bound_managed_runtime_backup,
)
}
fn finish_build_dir_with_backup_remover<F>(
plan: &ManagedRuntimeEnvPlan,
build_dir: PathBuf,
remove_backup: F,
) -> Result<(), String>
where
F: FnOnce(&Path, &ManagedFilesystemObjectIdentity) -> Result<(), String>,
{
write_expected_marker(&build_dir, &plan.expected_marker)?;
let _publish_lease = try_acquire_managed_runtime_env_publish_lease(plan)?;
let mut backup =
if managed_runtime_directory_path_is_directory(&plan.env_dir, "managed env directory")? {
let identity =
capture_managed_directory_identity(&plan.env_dir, "published managed environment")?;
let (recovery_center, recovery_permit) =
reserve_managed_runtime_backup_recovery_slot()?;
let sequence = allocate_managed_runtime_build_sequence()?;
let parent = plan.env_dir.parent().ok_or_else(|| {
format!(
"managed env directory has no parent: {}",
render_managed_runtime_path(&plan.env_dir)
)
})?;
let canonical_parent = fs::canonicalize(parent).map_err(|error| {
format!(
"failed to canonicalize managed environment parent {}: {error}",
render_managed_runtime_path(parent)
)
})?;
let backup = canonical_parent.join(format!(
".replaced-{}-{}-{sequence}",
plan.env_hash,
std::process::id()
));
fs::rename(&plan.env_dir, &backup).map_err(|error| {
format!(
"Failed to stage existing environment {} as {}: {}",
render_managed_runtime_path(&plan.env_dir),
render_managed_runtime_path(&backup),
error
)
})?;
Some((backup, identity, recovery_center, recovery_permit))
} else {
None
};
if let Err(publish_error) = fs::rename(&build_dir, &plan.env_dir) {
if let Some((backup_path, _, _, _)) = backup.as_ref() {
if let Err(rollback_error) = fs::rename(backup_path, &plan.env_dir) {
let (backup_path, identity, recovery_center, recovery_permit) = backup
.take()
.expect("present managed environment backup must remain owned");
recovery_center.enqueue(ManagedRuntimeBackupRecoveryRecord {
backup_path,
identity,
action: ManagedRuntimeBackupRecoveryAction::RestoreFailedPublication {
plan: Box::new(plan.clone()),
},
_permit: recovery_permit,
error_reported: false,
});
return Err(format!(
"Failed to publish {} as {}: {}; rollback also failed and was scheduled for persistent recovery: {}",
render_managed_runtime_path(&build_dir),
render_managed_runtime_path(&plan.env_dir),
publish_error,
rollback_error
));
}
drop(backup.take());
}
return Err(format!(
"Failed to publish {} as {}: {}",
render_managed_runtime_path(&build_dir),
render_managed_runtime_path(&plan.env_dir),
publish_error
));
}
if let Some((backup_path, identity, recovery_center, recovery_permit)) = backup.take() {
let immediate_cleanup = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
remove_backup(&backup_path, &identity)
}));
let recovery_error = match immediate_cleanup {
Ok(Ok(())) => None,
Ok(Err(error)) => Some(error),
Err(_) => Some("obsolete-backup cleanup panicked".to_string()),
};
if let Some(error) = recovery_error {
crate::runtime_logging::warn(format!(
"[LuaSkill:warn] managed environment publication committed; obsolete backup cleanup was scheduled for retry: {error}"
));
recovery_center.enqueue(ManagedRuntimeBackupRecoveryRecord {
backup_path,
identity,
action: ManagedRuntimeBackupRecoveryAction::RemoveCommittedBackup,
_permit: recovery_permit,
error_reported: true,
});
}
}
Ok(())
}
fn write_expected_marker(env_dir: &Path, marker: &ManagedRuntimeEnvMarker) -> Result<(), String> {
let marker_path = managed_env_marker_path(env_dir);
let text = serde_json::to_string_pretty(marker)
.map_err(|error| format!("Failed to serialize managed env marker: {}", error))?;
fs::write(&marker_path, format!("{}\n", text)).map_err(|error| {
format!(
"Failed to write {}: {}",
render_managed_runtime_path(&marker_path),
error
)
})
}
fn run_command(command: &mut Command, operation: &str) -> Result<(), String> {
let output = command
.output()
.map_err(|error| format!("Failed to {}: {}", operation, error))?;
if output.status.success() {
return Ok(());
}
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
Err(format!(
"Failed to {}: status={} stdout={} stderr={}",
operation, output.status, stdout, stderr
))
}
fn python_venv_executable(venv_dir: &Path) -> PathBuf {
if cfg!(windows) {
venv_dir.join("Scripts").join("python.exe")
} else {
venv_dir.join("bin").join("python")
}
}
fn package_store_dir_for_plan(plan: &ManagedRuntimeEnvPlan) -> PathBuf {
let family = match plan.runtime {
ManagedRuntimeKind::Python => "python",
ManagedRuntimeKind::Node => "node",
};
let store_name = match plan.runtime {
ManagedRuntimeKind::Python => "uv-cache",
ManagedRuntimeKind::Node => "pnpm-store",
};
plan.runtime_root
.join("dependencies")
.join("package_store")
.join(family)
.join(store_name)
}
fn managed_executable_parent(executable: &Path) -> Result<&Path, String> {
executable.parent().ok_or_else(|| {
format!(
"managed executable has no parent directory: {}",
render_managed_runtime_path(executable)
)
})
}
pub(crate) fn configure_managed_command_base_environment(
command: &mut Command,
controlled_home: &Path,
path_entries: &[PathBuf],
) -> Result<(), String> {
if !controlled_home.is_absolute() {
return Err(format!(
"managed command home is not absolute: {}",
render_managed_runtime_path(controlled_home)
));
}
if path_entries.is_empty() {
return Err("managed command PATH requires at least one controlled directory".to_string());
}
for directory in path_entries {
if !directory.is_absolute() {
return Err(format!(
"managed command PATH entry is not absolute: {}",
render_managed_runtime_path(directory)
));
}
}
command.env_clear();
let child_home = host_process_path_argument(controlled_home);
command.env("HOME", &child_home);
command.env("TMPDIR", &child_home);
let child_path_entries = path_entries
.iter()
.map(|path| host_process_path_argument(path))
.collect::<Vec<_>>();
let joined_path = env::join_paths(child_path_entries.iter())
.map_err(|error| format!("failed to construct controlled managed-runtime PATH: {error}"))?;
command.env("PATH", joined_path);
#[cfg(windows)]
install_required_windows_command_environment(command, &child_home)?;
Ok(())
}
#[cfg(windows)]
fn install_required_windows_command_environment(
command: &mut Command,
controlled_home: &Path,
) -> Result<(), String> {
let system_root = env::var_os("SystemRoot")
.ok_or_else(|| "required Windows environment variable SystemRoot is missing".to_string())?;
if !Path::new(&system_root).is_absolute() {
return Err(format!(
"Windows SystemRoot is not absolute: {}",
render_managed_runtime_path(Path::new(&system_root))
));
}
let canonical_system_root = fs::canonicalize(&system_root).map_err(|error| {
format!(
"failed to canonicalize Windows SystemRoot {}: {error}",
render_managed_runtime_path(Path::new(&system_root))
)
})?;
if !canonical_system_root.is_dir() {
return Err(format!(
"Windows SystemRoot is not a directory: {}",
render_managed_runtime_path(&canonical_system_root)
));
}
let com_spec = env::var_os("ComSpec")
.ok_or_else(|| "required Windows environment variable ComSpec is missing".to_string())?;
if !Path::new(&com_spec).is_absolute() {
return Err(format!(
"Windows ComSpec is not absolute: {}",
render_managed_runtime_path(Path::new(&com_spec))
));
}
let canonical_com_spec = fs::canonicalize(&com_spec).map_err(|error| {
format!(
"failed to canonicalize Windows ComSpec {}: {error}",
render_managed_runtime_path(Path::new(&com_spec))
)
})?;
if canonical_com_spec == canonical_system_root
|| !canonical_com_spec.starts_with(&canonical_system_root)
|| !canonical_com_spec.is_file()
{
return Err(format!(
"Windows ComSpec is not a regular file strictly inside SystemRoot: {}",
render_managed_runtime_path(&canonical_com_spec)
));
}
let child_system_root = PathBuf::from(
normalize_host_input_path_text(&system_root.to_string_lossy())
.map_err(|error| format!("SystemRoot: {error}"))?,
);
let child_com_spec = PathBuf::from(
normalize_host_input_path_text(&com_spec.to_string_lossy())
.map_err(|error| format!("ComSpec: {error}"))?,
);
command.env("SystemRoot", &child_system_root);
command.env("WINDIR", &child_system_root);
command.env("ComSpec", &child_com_spec);
command.env("PATHEXT", ".COM;.EXE;.BAT;.CMD");
if let Some(system_drive) = env::var_os("SystemDrive") {
let child_system_drive = normalize_host_input_path_text(&system_drive.to_string_lossy())
.map_err(|error| format!("SystemDrive: {error}"))?;
command.env("SystemDrive", child_system_drive);
}
command.env("USERPROFILE", controlled_home);
command.env("APPDATA", controlled_home);
command.env("LOCALAPPDATA", controlled_home);
command.env("TEMP", controlled_home);
command.env("TMP", controlled_home);
Ok(())
}
#[cfg(test)]
fn copy_dir_recursive(source: &Path, destination: &Path) -> Result<(), String> {
fs::create_dir_all(destination).map_err(|error| {
format!(
"Failed to create {}: {}",
render_managed_runtime_path(destination),
error
)
})?;
for entry in fs::read_dir(source).map_err(|error| {
format!(
"Failed to read {}: {}",
render_managed_runtime_path(source),
error
)
})? {
let entry = entry.map_err(|error| {
format!(
"Failed to read directory entry under {}: {}",
render_managed_runtime_path(source),
error
)
})?;
let source_path = entry.path();
let destination_path = destination.join(entry.file_name());
let file_type = entry.file_type().map_err(|error| {
format!(
"Failed to inspect {} under {}: {}",
render_managed_runtime_path(&source_path),
render_managed_runtime_path(source),
error
)
})?;
if file_type.is_dir() {
copy_dir_recursive(&source_path, &destination_path)?;
} else if file_type.is_file() {
fs::copy(&source_path, &destination_path).map_err(|error| {
format!(
"Failed to copy {} to {}: {}",
render_managed_runtime_path(&source_path),
render_managed_runtime_path(&destination_path),
error
)
})?;
} else {
return Err(format!(
"Failed to copy {} to {}: unsupported file type",
render_managed_runtime_path(&source_path),
render_managed_runtime_path(&destination_path)
));
}
}
Ok(())
}
pub(crate) fn resolve_python_env_plan(
package: &ManagedRuntimePackageContext,
spec: &PythonRuntimeDependencySpec,
) -> Result<ManagedRuntimeEnvPlan, String> {
validate_managed_runtime_resolver_version(&spec.version)?;
validate_managed_runtime_resolver_version(&spec.package_manager_version)?;
let managed_runtime_roots = package.managed_runtime_roots();
managed_runtime_roots.validate_live_filesystem_identity()?;
let platform = current_managed_runtime_platform_key()?;
let runtime_dir = runtime_install_dir(
managed_runtime_roots.distribution_root(),
"python",
&format!("cpython-{}-{}", spec.version, platform),
);
let runtime_install = resolve_managed_runtime_install_identity(
managed_runtime_roots.distribution_root(),
&runtime_dir,
"python",
&spec.version,
&platform,
)
.map_err(|error| {
format!(
"managed runtime distribution source={}: {error}",
managed_runtime_roots.distribution_source().as_str()
)
})?;
let package_manager = spec.package_manager.as_managed_runtime_str().to_string();
let package_manager_install_dir = runtime_install_dir(
managed_runtime_roots.distribution_root(),
"python",
&format!("uv-{}-{}", spec.package_manager_version, platform),
);
let package_manager_install = resolve_managed_runtime_install_identity(
managed_runtime_roots.distribution_root(),
&package_manager_install_dir,
"uv",
&spec.package_manager_version,
&platform,
)
.map_err(|error| {
format!(
"managed runtime distribution source={}: {error}",
managed_runtime_roots.distribution_source().as_str()
)
})?;
let lockfile_path = package.resolve_existing_file(&spec.lockfile, "python_runtime.lockfile")?;
let lock_hash = sha256_file(&lockfile_path)?;
let hash_input = ManagedRuntimeEnvHashInput {
runtime: ManagedRuntimeKind::Python,
runtime_version: spec.version.clone(),
platform,
package_manager,
package_manager_version: spec.package_manager_version.clone(),
lock_hash,
package_manifest_hash: None,
runtime_install_manifest_hash: runtime_install.manifest_hash.clone(),
runtime_executable_hash: runtime_install.executable_hash.clone(),
package_manager_install_manifest_hash: package_manager_install.manifest_hash.clone(),
package_manager_executable_hash: package_manager_install.executable_hash.clone(),
};
build_env_plan(
managed_runtime_roots,
ManagedRuntimeKind::Python,
runtime_install,
package_manager_install,
None,
lockfile_path,
hash_input,
)
}
pub(crate) fn validate_managed_node_runtime_version(version: &str) -> Result<(), String> {
let declared_version = Version::parse(version.trim()).map_err(|error| {
format!("node_runtime.version is not valid semantic version '{version}': {error}")
})?;
let minimum_version = Version::new(22, 0, 0);
if declared_version < minimum_version {
return Err(format!(
"managed Node runtime {version} is unsupported; minimum supported version is {MIN_MANAGED_NODE_RUNTIME_VERSION}"
));
}
Ok(())
}
pub(crate) fn resolve_node_env_plan(
package: &ManagedRuntimePackageContext,
spec: &NodeRuntimeDependencySpec,
) -> Result<ManagedRuntimeEnvPlan, String> {
validate_managed_node_runtime_version(&spec.version)?;
validate_managed_runtime_resolver_version(&spec.package_manager_version)?;
let managed_runtime_roots = package.managed_runtime_roots();
managed_runtime_roots.validate_live_filesystem_identity()?;
let platform = current_managed_runtime_platform_key()?;
let runtime_dir = runtime_install_dir(
managed_runtime_roots.distribution_root(),
"node",
&format!("node-{}-{}", spec.version, platform),
);
let runtime_install = resolve_managed_runtime_install_identity(
managed_runtime_roots.distribution_root(),
&runtime_dir,
"node",
&spec.version,
&platform,
)
.map_err(|error| {
format!(
"managed runtime distribution source={}: {error}",
managed_runtime_roots.distribution_source().as_str()
)
})?;
let package_manager = spec.package_manager.as_managed_runtime_str().to_string();
let package_manager_install_dir = runtime_install_dir(
managed_runtime_roots.distribution_root(),
"node",
&format!("pnpm-{}", spec.package_manager_version),
);
let package_manager_install = resolve_managed_runtime_install_identity(
managed_runtime_roots.distribution_root(),
&package_manager_install_dir,
"pnpm",
&spec.package_manager_version,
"any",
)
.map_err(|error| {
format!(
"managed runtime distribution source={}: {error}",
managed_runtime_roots.distribution_source().as_str()
)
})?;
let package_manifest_path = if spec.package_json.trim().is_empty() {
None
} else {
Some(package.resolve_existing_file(&spec.package_json, "node_runtime.package_json")?)
};
let package_manifest_hash = package_manifest_path
.as_ref()
.map(|path| sha256_file(path))
.transpose()?;
let lockfile_path = package.resolve_existing_file(&spec.lockfile, "node_runtime.lockfile")?;
let lock_hash = sha256_file(&lockfile_path)?;
let hash_input = ManagedRuntimeEnvHashInput {
runtime: ManagedRuntimeKind::Node,
runtime_version: spec.version.clone(),
platform,
package_manager,
package_manager_version: spec.package_manager_version.clone(),
lock_hash,
package_manifest_hash,
runtime_install_manifest_hash: runtime_install.manifest_hash.clone(),
runtime_executable_hash: runtime_install.executable_hash.clone(),
package_manager_install_manifest_hash: package_manager_install.manifest_hash.clone(),
package_manager_executable_hash: package_manager_install.executable_hash.clone(),
};
build_env_plan(
managed_runtime_roots,
ManagedRuntimeKind::Node,
runtime_install,
package_manager_install,
package_manifest_path,
lockfile_path,
hash_input,
)
}
pub fn read_install_manifest(install_dir: &Path) -> Result<ManagedRuntimeInstallManifest, String> {
read_install_manifest_with_hash(install_dir).map(|(manifest, _)| manifest)
}
fn read_install_manifest_with_hash(
install_dir: &Path,
) -> Result<(ManagedRuntimeInstallManifest, String), String> {
let manifest_path = install_dir.join("runtime-manifest.json");
managed_runtime_install_manifest_path_is_file(&manifest_path)?;
let manifest_bytes = fs::read(&manifest_path).map_err(|error| {
format!(
"Failed to read {}: {}",
render_managed_runtime_path(&manifest_path),
error
)
})?;
let manifest_hash = sha256_hex(&manifest_bytes);
let parse_bytes = manifest_bytes
.strip_prefix(&[0xEF, 0xBB, 0xBF])
.unwrap_or(&manifest_bytes);
let manifest = serde_json::from_slice(parse_bytes).map_err(|error| {
format!(
"Failed to parse {}: {}",
render_managed_runtime_path(&manifest_path),
error
)
})?;
Ok((manifest, manifest_hash))
}
fn managed_runtime_install_manifest_path_is_file(manifest_path: &Path) -> Result<(), String> {
match fs::symlink_metadata(manifest_path) {
Ok(metadata) if metadata.file_type().is_symlink() => Err(format!(
"managed runtime install manifest must not be a symbolic link: {}",
render_managed_runtime_path(manifest_path)
)),
Ok(metadata) if metadata.is_file() => Ok(()),
Ok(_) => Err(format!(
"managed runtime install manifest is not a file: {}",
render_managed_runtime_path(manifest_path)
)),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Err(format!(
"managed runtime install manifest not found: {}",
render_managed_runtime_path(manifest_path)
)),
Err(error) => Err(format!(
"Failed to inspect managed runtime install manifest {}: {}",
render_managed_runtime_path(manifest_path),
error
)),
}
}
fn build_env_plan(
managed_runtime_roots: Arc<ManagedRuntimeRoots>,
runtime: ManagedRuntimeKind,
runtime_install: ManagedRuntimeInstallIdentity,
package_manager_install: ManagedRuntimeInstallIdentity,
package_manifest_path: Option<PathBuf>,
lockfile_path: PathBuf,
hash_input: ManagedRuntimeEnvHashInput,
) -> Result<ManagedRuntimeEnvPlan, String> {
let env_hash = compute_managed_runtime_env_hash(&hash_input);
let env_dir = managed_env_dir(
managed_runtime_roots.environment_root(),
runtime,
&hash_input.runtime_version,
&env_hash,
);
let expected_marker = ManagedRuntimeEnvMarker::expected(&hash_input, env_hash.clone());
Ok(ManagedRuntimeEnvPlan {
runtime,
platform: hash_input.platform,
runtime_root: managed_runtime_roots.runtime_root().to_path_buf(),
distribution_root: managed_runtime_roots.distribution_root().to_path_buf(),
environment_root: managed_runtime_roots.environment_root().to_path_buf(),
distribution_source: managed_runtime_roots.distribution_source(),
environment_source: managed_runtime_roots.environment_source(),
runtime_install_root: runtime_install.install_root,
runtime_version: hash_input.runtime_version,
runtime_executable: runtime_install.executable,
runtime_install_manifest_hash: hash_input.runtime_install_manifest_hash,
runtime_executable_hash: hash_input.runtime_executable_hash,
package_manager: hash_input.package_manager,
package_manager_version: hash_input.package_manager_version,
package_manager_executable: package_manager_install.executable,
package_manager_install_root: package_manager_install.install_root,
package_manager_install_manifest_hash: hash_input.package_manager_install_manifest_hash,
package_manager_executable_hash: hash_input.package_manager_executable_hash,
managed_runtime_roots,
package_manifest_path,
lockfile_path,
lock_hash: hash_input.lock_hash,
package_manifest_hash: hash_input.package_manifest_hash,
env_hash,
env_dir,
expected_marker,
})
}
fn runtime_install_dir(distribution_root: &Path, family: &str, name: &str) -> PathBuf {
distribution_root.join(family).join(name)
}
fn resolve_managed_runtime_install_identity(
distribution_root: &Path,
install_dir: &Path,
expected_runtime: &str,
expected_version: &str,
expected_platform: &str,
) -> Result<ManagedRuntimeInstallIdentity, String> {
let live_distribution_root = fs::canonicalize(distribution_root).map_err(|error| {
format!(
"Failed to canonicalize managed runtime distribution root {}: {}",
render_managed_runtime_path(distribution_root),
error
)
})?;
if live_distribution_root != distribution_root {
return Err(format!(
"managed runtime distribution root changed canonical identity: expected {}, got {}",
render_managed_runtime_path(distribution_root),
render_managed_runtime_path(&live_distribution_root)
));
}
ensure_managed_runtime_directory(&live_distribution_root, "managed runtime distribution root")?;
let canonical_install_root = fs::canonicalize(install_dir).map_err(|error| {
format!(
"Failed to canonicalize managed runtime install directory {}: {}",
render_managed_runtime_path(install_dir),
error
)
})?;
if canonical_install_root == live_distribution_root
|| !canonical_install_root.starts_with(&live_distribution_root)
{
return Err(format!(
"managed runtime install directory {} escapes distribution root {}",
render_managed_runtime_path(&canonical_install_root),
render_managed_runtime_path(&live_distribution_root)
));
}
ensure_managed_runtime_directory(&canonical_install_root, "managed runtime install directory")?;
let (manifest, manifest_hash) = read_install_manifest_with_hash(&canonical_install_root)?;
let executable = resolve_install_executable_from_manifest(
&canonical_install_root,
&manifest,
expected_runtime,
expected_version,
expected_platform,
)?;
let executable_hash = sha256_file(&executable)?;
Ok(ManagedRuntimeInstallIdentity {
install_root: canonical_install_root,
executable,
manifest_hash,
executable_hash,
})
}
#[cfg(test)]
fn resolve_install_executable(
install_dir: &Path,
expected_runtime: &str,
expected_version: &str,
expected_platform: &str,
) -> Result<PathBuf, String> {
let manifest = read_install_manifest(install_dir)?;
resolve_install_executable_from_manifest(
install_dir,
&manifest,
expected_runtime,
expected_version,
expected_platform,
)
}
fn resolve_install_executable_from_manifest(
install_dir: &Path,
manifest: &ManagedRuntimeInstallManifest,
expected_runtime: &str,
expected_version: &str,
expected_platform: &str,
) -> Result<PathBuf, String> {
if manifest.schema_version != 1 {
return Err(format!(
"managed runtime manifest {} uses unsupported schema_version {}",
render_managed_runtime_path(install_dir),
manifest.schema_version
));
}
if manifest.runtime != expected_runtime {
return Err(format!(
"managed runtime manifest {} has runtime '{}', expected '{}'",
render_managed_runtime_path(install_dir),
manifest.runtime,
expected_runtime
));
}
if manifest.version != expected_version {
return Err(format!(
"managed runtime manifest {} has version '{}', expected '{}'",
render_managed_runtime_path(install_dir),
manifest.version,
expected_version
));
}
if manifest.platform != expected_platform {
return Err(format!(
"managed runtime manifest {} has platform '{}', expected '{}'",
render_managed_runtime_path(install_dir),
manifest.platform,
expected_platform
));
}
let relative_executable = Path::new(&manifest.executable);
if !managed_install_executable_relative_path_is_safe(relative_executable) {
return Err(format!(
"managed runtime manifest {} has unsafe relative executable path '{}'",
render_managed_runtime_path(install_dir),
manifest.executable
));
}
let canonical_install_dir = fs::canonicalize(install_dir).map_err(|error| {
format!(
"Failed to canonicalize managed runtime install directory {}: {}",
render_managed_runtime_path(install_dir),
error
)
})?;
if !canonical_install_dir.is_dir() {
return Err(format!(
"managed runtime install path is not a directory: {}",
render_managed_runtime_path(&canonical_install_dir)
));
}
let executable_candidate = canonical_install_dir.join(relative_executable);
let canonical_executable = fs::canonicalize(&executable_candidate).map_err(|error| {
format!(
"Failed to canonicalize managed runtime executable {}: {}",
render_managed_runtime_path(&executable_candidate),
error
)
})?;
if canonical_executable == canonical_install_dir
|| !canonical_executable.starts_with(&canonical_install_dir)
{
return Err(format!(
"managed runtime executable {} escapes install directory {}",
render_managed_runtime_path(&canonical_executable),
render_managed_runtime_path(&canonical_install_dir)
));
}
let executable_metadata = fs::metadata(&canonical_executable).map_err(|error| {
format!(
"Failed to inspect managed runtime executable {}: {}",
render_managed_runtime_path(&canonical_executable),
error
)
})?;
if !executable_metadata.is_file() {
return Err(format!(
"managed runtime executable is not a file: {}",
render_managed_runtime_path(&canonical_executable)
));
}
Ok(canonical_executable)
}
fn managed_install_executable_relative_path_is_safe(path: &Path) -> bool {
!path.as_os_str().is_empty()
&& path.components().all(|component| match component {
Component::Normal(value) => managed_install_executable_component_is_safe(value),
_ => false,
})
}
#[cfg(windows)]
fn managed_install_executable_component_is_safe(component: &std::ffi::OsStr) -> bool {
use std::os::windows::ffi::OsStrExt;
!component.encode_wide().any(|unit| unit == u16::from(b':'))
}
#[cfg(not(windows))]
fn managed_install_executable_component_is_safe(_component: &std::ffi::OsStr) -> bool {
true
}
fn hash_field(hasher: &mut Sha256, label: &str, value: impl AsRef<str>) {
hasher.update(label.as_bytes());
hasher.update([0]);
hasher.update(value.as_ref().as_bytes());
hasher.update([0xff]);
}
fn runtime_prefix(runtime: ManagedRuntimeKind) -> &'static str {
match runtime {
ManagedRuntimeKind::Python => "py",
ManagedRuntimeKind::Node => "node",
}
}
#[cfg(test)]
mod tests {
#[cfg(windows)]
use super::managed_install_executable_relative_path_is_safe;
use super::{
ManagedRuntimeBackupRecoveryAction, ManagedRuntimeBackupRecoveryRecord,
ManagedRuntimeEnvHashInput, ManagedRuntimeEnvMarker, ManagedRuntimeEnvPlan,
ManagedRuntimeKind, ManagedRuntimeRootSource, ManagedRuntimeRoots,
acquire_managed_runtime_env_file_lock, acquire_ready_managed_env_lease,
capture_managed_directory_identity, compute_managed_runtime_env_hash,
configure_managed_command_base_environment, copy_dir_recursive,
current_managed_runtime_platform_key, finish_build_dir,
finish_build_dir_with_backup_remover, managed_env_dir, managed_env_is_ready,
managed_env_marker_matches, managed_env_marker_path, prepare_build_dir_with_sequence,
read_install_manifest, reserve_managed_runtime_backup_recovery_slot,
resolve_install_executable, resolve_managed_runtime_install,
resolve_managed_runtime_install_identity, resolve_node_env_plan, resolve_python_env_plan,
sha256_file, sha256_hex, validate_managed_runtime_plan_assets,
verify_managed_runtime_build_input_hash, write_expected_marker,
};
use crate::runtime::managed_package::ManagedRuntimePackageContext;
use crate::runtime::path::render_host_visible_path;
use crate::skill::dependencies::{
NodeRuntimeDependencySpec, NodeRuntimePackageManager, PythonRuntimeDependencySpec,
PythonRuntimePackageManager,
};
use std::fs;
#[cfg(unix)]
use std::os::unix::fs::symlink as create_unix_symlink;
#[cfg(windows)]
use std::os::windows::fs::{
symlink_dir as create_windows_directory_symlink,
symlink_file as create_windows_file_symlink,
};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
#[cfg(unix)]
fn create_test_file_symlink(link_path: &Path, target_path: &Path) -> bool {
create_unix_symlink(target_path, link_path).expect("create test file symlink");
true
}
#[cfg(unix)]
fn create_test_directory_symlink(link_path: &Path, target_path: &Path) -> bool {
create_unix_symlink(target_path, link_path).expect("create test directory symlink");
true
}
fn make_test_package_context(
package_root: &Path,
runtime_root: &Path,
) -> Arc<ManagedRuntimePackageContext> {
fs::create_dir_all(package_root).expect("create managed runtime test package root");
fs::create_dir_all(runtime_root).expect("create managed runtime test runtime root");
ManagedRuntimePackageContext::for_skill(
"managed-runtime-test",
package_root,
runtime_root,
None,
)
.expect("build managed runtime test package context")
}
#[cfg(windows)]
fn should_skip_windows_symlink_test(error: &std::io::Error) -> bool {
const ERROR_PRIVILEGE_NOT_HELD: i32 = 1314;
error.kind() == std::io::ErrorKind::PermissionDenied
|| error.raw_os_error() == Some(ERROR_PRIVILEGE_NOT_HELD)
}
#[cfg(windows)]
fn create_test_file_symlink(link_path: &Path, target_path: &Path) -> bool {
match create_windows_file_symlink(target_path, link_path) {
Ok(()) => true,
Err(error) if should_skip_windows_symlink_test(&error) => {
eprintln!(
"skip symlink-dependent test because Windows symlink privileges are unavailable: {error}"
);
false
}
Err(error) => panic!("create test file symlink: {error}"),
}
}
#[cfg(windows)]
fn create_test_directory_symlink(link_path: &Path, target_path: &Path) -> bool {
match create_windows_directory_symlink(target_path, link_path) {
Ok(()) => true,
Err(error) if should_skip_windows_symlink_test(&error) => {
eprintln!(
"skip directory-symlink test because Windows symlink privileges are unavailable: {error}"
);
false
}
Err(error) => panic!("create test directory symlink: {error}"),
}
}
#[test]
fn env_hash_is_stable_and_lock_sensitive() {
let input = ManagedRuntimeEnvHashInput {
runtime: ManagedRuntimeKind::Python,
runtime_version: "3.12.8".to_string(),
platform: "windows-x64".to_string(),
package_manager: "uv".to_string(),
package_manager_version: "0.11.28".to_string(),
lock_hash: sha256_hex(b"requests==2.32.3"),
package_manifest_hash: None,
runtime_install_manifest_hash: "python-manifest".to_string(),
runtime_executable_hash: "python-executable".to_string(),
package_manager_install_manifest_hash: "uv-manifest".to_string(),
package_manager_executable_hash: "uv-executable".to_string(),
};
let same_hash = compute_managed_runtime_env_hash(&input);
let repeated_hash = compute_managed_runtime_env_hash(&input);
let changed_hash = compute_managed_runtime_env_hash(&ManagedRuntimeEnvHashInput {
lock_hash: sha256_hex(b"requests==2.32.4"),
..input.clone()
});
let changed_runtime_hash = compute_managed_runtime_env_hash(&ManagedRuntimeEnvHashInput {
runtime_executable_hash: "different-python-executable".to_string(),
..input
});
assert_eq!(same_hash, repeated_hash);
assert_ne!(same_hash, changed_hash);
assert_ne!(same_hash, changed_runtime_hash);
}
#[test]
fn managed_runtime_build_input_requires_exact_planned_hash() {
let root = make_test_root("verified-build-input");
fs::create_dir_all(&root).expect("create verified build input root");
let private_copy = root.join("dependency.lock");
fs::write(&private_copy, b"version=one").expect("write planned build input");
let expected_hash = sha256_hex(b"version=one");
verify_managed_runtime_build_input_hash(
&private_copy,
&expected_hash,
"test dependency input",
)
.expect("accept exact planned build input");
fs::write(&private_copy, b"version=two").expect("replace copied build input bytes");
let error = verify_managed_runtime_build_input_hash(
&private_copy,
&expected_hash,
"test dependency input",
)
.expect_err("reject build input changed after planning");
assert!(error.contains("changed after environment planning"));
let _ = fs::remove_dir_all(root);
}
#[test]
fn marker_match_requires_exact_identity() {
let input = ManagedRuntimeEnvHashInput {
runtime: ManagedRuntimeKind::Node,
runtime_version: "24.18.0".to_string(),
platform: "linux-x64".to_string(),
package_manager: "pnpm".to_string(),
package_manager_version: "11.11.0".to_string(),
lock_hash: "lock".to_string(),
package_manifest_hash: Some("package".to_string()),
runtime_install_manifest_hash: "node-manifest".to_string(),
runtime_executable_hash: "node-executable".to_string(),
package_manager_install_manifest_hash: "pnpm-manifest".to_string(),
package_manager_executable_hash: "pnpm-executable".to_string(),
};
let env_hash = compute_managed_runtime_env_hash(&input);
let expected = ManagedRuntimeEnvMarker::expected(&input, env_hash);
let mut actual = expected.clone();
assert!(managed_env_marker_matches(&actual, &expected));
actual.lock_hash = "other".to_string();
assert!(!managed_env_marker_matches(&actual, &expected));
}
#[test]
fn managed_env_dir_uses_runtime_version_group() {
let path = managed_env_dir(
Path::new("runtime-root"),
ManagedRuntimeKind::Python,
"3.12.8",
"abc",
);
assert!(path.ends_with(Path::new("python/py-3.12.8/abc")));
}
#[test]
fn managed_env_ready_checks_expected_marker() {
let platform = current_managed_runtime_platform_key().expect("platform should resolve");
let root = make_test_root("ready-marker");
let runtime_root = root.join("runtime");
let package_root = root.join("skill");
fs::create_dir_all(package_root.join("python")).unwrap();
fs::write(
package_root.join("python/requirements.lock"),
b"requests==2.32.3",
)
.unwrap();
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("cpython-3.14.4-{}", platform)),
"python",
"3.14.4",
&platform,
platform_executable("python"),
);
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("uv-0.11.28-{}", platform)),
"uv",
"0.11.28",
&platform,
platform_executable("uv"),
);
let package = make_test_package_context(&package_root, &runtime_root);
let plan = resolve_python_env_plan(
package.as_ref(),
&PythonRuntimeDependencySpec {
version: "3.14.4".to_string(),
package_manager: PythonRuntimePackageManager::Uv,
package_manager_version: "0.11.28".to_string(),
lockfile: "python/requirements.lock".to_string(),
required: true,
},
)
.expect("python env plan should resolve");
assert!(!managed_env_is_ready(&plan).expect("ready check should work"));
fs::create_dir_all(&plan.env_dir).unwrap();
write_expected_marker(&plan.env_dir, &plan.expected_marker).unwrap();
assert!(managed_env_is_ready(&plan).expect("ready check should work"));
let _ = fs::remove_dir_all(root);
}
#[test]
fn managed_env_ready_rejects_symbolic_link_environment_directory() {
let root = make_test_root("symlink-environment-directory");
let plan = make_environment_file_lock_test_plan(&root);
let outside = root.join("outside-environment");
fs::create_dir_all(plan.env_dir.parent().expect("managed env parent"))
.expect("create managed env parent");
fs::create_dir(&outside).expect("create outside environment");
write_expected_marker(&outside, &plan.expected_marker)
.expect("write forged outside environment marker");
if !create_test_directory_symlink(&plan.env_dir, &outside) {
let _ = fs::remove_dir_all(root);
return;
}
let error = managed_env_is_ready(&plan)
.expect_err("symbolic-link environment directory must be rejected");
assert!(error.contains("must not be a symbolic link"));
let _ = fs::remove_dir_all(root);
}
#[test]
fn managed_env_ready_rejects_symbolic_link_marker() {
let root = make_test_root("symlink-environment-marker");
let plan = make_environment_file_lock_test_plan(&root);
fs::create_dir_all(&plan.env_dir).expect("create real managed environment");
let outside = root.join("outside-marker-root");
fs::create_dir(&outside).expect("create outside marker root");
write_expected_marker(&outside, &plan.expected_marker)
.expect("write forged outside marker");
let outside_marker = managed_env_marker_path(&outside);
let marker_link = managed_env_marker_path(&plan.env_dir);
if !create_test_file_symlink(&marker_link, &outside_marker) {
let _ = fs::remove_dir_all(root);
return;
}
let error = managed_env_is_ready(&plan)
.expect_err("symbolic-link environment marker must be rejected");
assert!(error.contains("must not be a symbolic link"));
let _ = fs::remove_dir_all(root);
}
#[test]
fn managed_env_ready_rejects_directory_marker() {
let platform = current_managed_runtime_platform_key().expect("platform should resolve");
let root = make_test_root("directory-marker");
let runtime_root = root.join("runtime");
let package_root = root.join("skill");
fs::create_dir_all(package_root.join("python")).unwrap();
fs::write(
package_root.join("python/requirements.lock"),
b"requests==2.32.3",
)
.unwrap();
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("cpython-3.14.4-{}", platform)),
"python",
"3.14.4",
&platform,
platform_executable("python"),
);
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("uv-0.11.28-{}", platform)),
"uv",
"0.11.28",
&platform,
platform_executable("uv"),
);
let package = make_test_package_context(&package_root, &runtime_root);
let plan = resolve_python_env_plan(
package.as_ref(),
&PythonRuntimeDependencySpec {
version: "3.14.4".to_string(),
package_manager: PythonRuntimePackageManager::Uv,
package_manager_version: "0.11.28".to_string(),
lockfile: "python/requirements.lock".to_string(),
required: true,
},
)
.expect("python env plan should resolve");
let marker_path = managed_env_marker_path(&plan.env_dir);
fs::create_dir_all(&marker_path).expect("directory marker should be created");
let error =
managed_env_is_ready(&plan).expect_err("directory marker readiness check should fail");
assert!(
error.contains("Managed runtime env marker is not a file"),
"unexpected error: {}",
error
);
assert!(
error.contains(&render_host_visible_path(&marker_path)),
"unexpected error: {}",
error
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn read_install_manifest_rejects_directory_manifest() {
let root = make_test_root("directory-install-manifest");
let install_dir = root.join("runtime-install");
let manifest_path = install_dir.join("runtime-manifest.json");
fs::create_dir_all(&manifest_path).expect("directory install manifest should be created");
let error =
read_install_manifest(&install_dir).expect_err("directory manifest should fail");
assert!(
error.contains("managed runtime install manifest is not a file"),
"unexpected error: {}",
error
);
assert!(
error.contains(&render_host_visible_path(&manifest_path)),
"unexpected error: {}",
error
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn install_manifest_rejects_unsafe_relative_executable_paths() {
let root = make_test_root("unsafe-install-executable-path");
let install_dir = root.join("runtime-install");
fs::create_dir_all(&install_dir).expect("create unsafe manifest install directory");
let outside_executable = root.join("outside-tool");
fs::write(&outside_executable, b"outside").expect("write outside executable fixture");
let unsafe_paths = [
outside_executable.to_string_lossy().into_owned(),
"../outside-tool".to_string(),
"bin/../outside-tool".to_string(),
"./tool".to_string(),
];
for unsafe_path in unsafe_paths {
let payload = serde_json::json!({
"schema_version": 1,
"runtime": "tool",
"version": "1.0.0",
"platform": "test",
"executable": unsafe_path,
});
fs::write(
install_dir.join("runtime-manifest.json"),
serde_json::to_vec_pretty(&payload).expect("encode unsafe install manifest"),
)
.expect("write unsafe install manifest");
let error = resolve_install_executable(&install_dir, "tool", "1.0.0", "test")
.expect_err("unsafe install executable path should fail");
assert!(
error.contains("unsafe relative executable path"),
"unexpected error for {unsafe_path:?}: {error}"
);
}
let _ = fs::remove_dir_all(root);
}
#[cfg(windows)]
#[test]
fn install_manifest_rejects_windows_alternate_data_stream_path() {
assert!(!managed_install_executable_relative_path_is_safe(
Path::new("bin/tool.exe:payload")
));
}
#[test]
fn install_manifest_rejects_executable_symlink_escape() {
let root = make_test_root("install-executable-symlink-escape");
let install_dir = root.join("runtime-install");
fs::create_dir_all(install_dir.join("bin")).expect("create symlink install bin directory");
let outside_executable = root.join("outside-tool");
fs::write(&outside_executable, b"outside").expect("write symlink target fixture");
let executable_link = install_dir.join("bin/tool");
if !create_test_file_symlink(&executable_link, &outside_executable) {
let _ = fs::remove_dir_all(root);
return;
}
let payload = serde_json::json!({
"schema_version": 1,
"runtime": "tool",
"version": "1.0.0",
"platform": "test",
"executable": "bin/tool",
});
fs::write(
install_dir.join("runtime-manifest.json"),
serde_json::to_vec_pretty(&payload).expect("encode symlink escape manifest"),
)
.expect("write symlink escape manifest");
let error = resolve_install_executable(&install_dir, "tool", "1.0.0", "test")
.expect_err("symlink escape should fail");
assert!(
error.contains("escapes install directory"),
"unexpected error: {error}"
);
assert!(
error.contains(&render_host_visible_path(
&fs::canonicalize(&outside_executable).expect("canonical outside executable")
)),
"unexpected error: {error}"
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn managed_command_environment_hides_host_secrets_and_keeps_controlled_path() {
let root = make_test_root("managed-command-environment");
let controlled_home = root.join("home");
let first_bin = root.join("runtime-bin");
let second_bin = root.join("package-manager-bin");
fs::create_dir_all(&controlled_home).expect("create controlled command home");
fs::create_dir_all(&first_bin).expect("create first controlled bin");
fs::create_dir_all(&second_bin).expect("create second controlled bin");
let current_executable = std::env::current_exe().expect("resolve current test executable");
let mut command = Command::new(¤t_executable);
command
.arg("--list")
.env("AWS_SECRET_ACCESS_KEY", "must-not-cross-managed-boundary");
configure_managed_command_base_environment(
&mut command,
&controlled_home,
&[first_bin.clone(), second_bin.clone()],
)
.expect("configure isolated managed command environment");
let command_environment = command
.get_envs()
.map(|(key, value)| {
(
key.to_string_lossy().into_owned(),
value.map(|value| value.to_os_string()),
)
})
.collect::<std::collections::HashMap<_, _>>();
assert!(!command_environment.contains_key("AWS_SECRET_ACCESS_KEY"));
let expected_path =
std::env::join_paths([&first_bin, &second_bin]).expect("join expected controlled PATH");
assert_eq!(
command_environment
.get("PATH")
.and_then(Option::as_ref)
.expect("controlled PATH environment"),
&expected_path
);
assert_eq!(
command_environment
.get("HOME")
.and_then(Option::as_ref)
.expect("controlled HOME environment"),
controlled_home.as_os_str()
);
let status = command
.status()
.expect("launch with managed command environment");
assert!(
status.success(),
"minimal managed environment should launch"
);
let _ = fs::remove_dir_all(root);
}
fn make_environment_file_lock_test_plan(root: &Path) -> ManagedRuntimeEnvPlan {
let distribution_root = root.join("runtimes");
let environment_root = root.join("managed-envs");
fs::create_dir_all(&environment_root).expect("create lock test environment root");
let runtime_install_root = distribution_root
.join("python")
.join("cpython-3.14.4-test-platform");
let package_manager_install_root = distribution_root
.join("python")
.join("uv-0.11.28-test-platform");
write_install_manifest(
&runtime_install_root,
"python",
"3.14.4",
"test-platform",
platform_executable("python"),
);
write_install_manifest(
&package_manager_install_root,
"uv",
"0.11.28",
"test-platform",
platform_executable("uv"),
);
let managed_runtime_roots = Arc::new(
ManagedRuntimeRoots::new(root, Some(&distribution_root), Some(&environment_root))
.expect("create lock test managed runtime roots"),
);
let runtime_install = resolve_managed_runtime_install_identity(
managed_runtime_roots.distribution_root(),
&runtime_install_root,
"python",
"3.14.4",
"test-platform",
)
.expect("resolve lock test Python install");
let package_manager_install = resolve_managed_runtime_install_identity(
managed_runtime_roots.distribution_root(),
&package_manager_install_root,
"uv",
"0.11.28",
"test-platform",
)
.expect("resolve lock test uv install");
let hash_input = ManagedRuntimeEnvHashInput {
runtime: ManagedRuntimeKind::Python,
runtime_version: "3.14.4".to_string(),
platform: "test-platform".to_string(),
package_manager: "uv".to_string(),
package_manager_version: "0.11.28".to_string(),
lock_hash: "lock".to_string(),
package_manifest_hash: None,
runtime_install_manifest_hash: runtime_install.manifest_hash.clone(),
runtime_executable_hash: runtime_install.executable_hash.clone(),
package_manager_install_manifest_hash: package_manager_install.manifest_hash.clone(),
package_manager_executable_hash: package_manager_install.executable_hash.clone(),
};
let env_hash = "cross-process-lock-hash".to_string();
ManagedRuntimeEnvPlan {
runtime: ManagedRuntimeKind::Python,
platform: hash_input.platform.clone(),
runtime_root: managed_runtime_roots.runtime_root().to_path_buf(),
distribution_root: managed_runtime_roots.distribution_root().to_path_buf(),
environment_root: managed_runtime_roots.environment_root().to_path_buf(),
distribution_source: ManagedRuntimeRootSource::HostConfigured,
environment_source: ManagedRuntimeRootSource::HostConfigured,
runtime_install_root: runtime_install.install_root,
runtime_version: hash_input.runtime_version.clone(),
runtime_executable: runtime_install.executable,
runtime_install_manifest_hash: hash_input.runtime_install_manifest_hash.clone(),
runtime_executable_hash: hash_input.runtime_executable_hash.clone(),
package_manager: hash_input.package_manager.clone(),
package_manager_version: hash_input.package_manager_version.clone(),
package_manager_executable: package_manager_install.executable,
package_manager_install_root: package_manager_install.install_root,
package_manager_install_manifest_hash: hash_input
.package_manager_install_manifest_hash
.clone(),
package_manager_executable_hash: hash_input.package_manager_executable_hash.clone(),
managed_runtime_roots: Arc::clone(&managed_runtime_roots),
package_manifest_path: None,
lockfile_path: root.join("requirements.lock"),
lock_hash: hash_input.lock_hash.clone(),
package_manifest_hash: None,
env_hash: env_hash.clone(),
env_dir: managed_env_dir(
managed_runtime_roots.environment_root(),
ManagedRuntimeKind::Python,
&hash_input.runtime_version,
&env_hash,
),
expected_marker: ManagedRuntimeEnvMarker::expected(&hash_input, env_hash),
}
}
#[test]
fn managed_runtime_environment_file_lock_child_probe() {
let Some(root) = std::env::var_os("LUASKILLS_TEST_ENV_LOCK_ROOT") else {
return;
};
let root = PathBuf::from(root);
let started_path = root.join("child-started");
let acquired_path = root.join("child-acquired");
fs::write(&started_path, b"started").expect("write child lock start signal");
let plan = make_environment_file_lock_test_plan(&root);
let guard = acquire_managed_runtime_env_file_lock(&plan)
.expect("child acquires managed environment file lock");
fs::write(&acquired_path, b"acquired").expect("write child lock acquisition signal");
drop(guard);
}
#[test]
fn managed_runtime_environment_file_lock_serializes_processes() {
let root = make_test_root("cross-process-env-lock");
fs::create_dir_all(&root).expect("create file-lock test root");
let plan = make_environment_file_lock_test_plan(&root);
let first_guard = acquire_managed_runtime_env_file_lock(&plan)
.expect("acquire first managed environment file lock");
let started_path = root.join("child-started");
let acquired_path = root.join("child-acquired");
let current_test_executable =
std::env::current_exe().expect("resolve current test executable");
let mut child = Command::new(current_test_executable)
.arg("runtime::managed_runtime::tests::managed_runtime_environment_file_lock_child_probe")
.arg("--exact")
.arg("--nocapture")
.env("LUASKILLS_TEST_ENV_LOCK_ROOT", &root)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("spawn cross-process file-lock probe");
let start_deadline = std::time::Instant::now() + Duration::from_secs(60);
while !started_path.exists() && std::time::Instant::now() < start_deadline {
thread::sleep(Duration::from_millis(10));
}
assert!(started_path.exists(), "child lock probe must start");
thread::sleep(Duration::from_millis(150));
assert!(
!acquired_path.exists(),
"child process must block while the parent lock is held"
);
drop(first_guard);
let exit_deadline = std::time::Instant::now() + Duration::from_secs(10);
let status = loop {
if let Some(status) = child.try_wait().expect("poll file-lock child") {
break status;
}
if std::time::Instant::now() >= exit_deadline {
let _ = child.kill();
let reap_deadline = std::time::Instant::now() + Duration::from_secs(5);
while child.try_wait().ok().flatten().is_none()
&& std::time::Instant::now() < reap_deadline
{
thread::sleep(Duration::from_millis(10));
}
panic!("file-lock child did not finish after parent released the lock");
}
thread::sleep(Duration::from_millis(10));
};
assert!(status.success(), "file-lock child failed: {status}");
assert!(
acquired_path.exists(),
"child must acquire the released lock"
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn managed_runtime_environment_lifecycle_lease_child_probe() {
let Some(root) = std::env::var_os("LUASKILLS_TEST_ENV_LEASE_ROOT") else {
return;
};
let root = PathBuf::from(root);
let plan = make_environment_file_lock_test_plan(&root);
let lease = acquire_ready_managed_env_lease(&plan)
.expect("child acquires shared managed environment lifecycle lease");
let acquired_path = root.join("lease-acquired");
fs::write(&acquired_path, b"acquired").expect("write lifecycle lease acquisition signal");
let release_path = root.join("lease-release");
let deadline = std::time::Instant::now() + Duration::from_secs(60);
while !release_path.exists() && std::time::Instant::now() < deadline {
thread::sleep(Duration::from_millis(10));
}
assert!(
release_path.exists(),
"parent must release lifecycle lease probe"
);
drop(lease);
}
#[test]
fn managed_runtime_environment_lifecycle_lease_guards_publication() {
let root = make_test_root("cross-process-env-lifecycle-lease");
fs::create_dir_all(&root).expect("create lifecycle lease test root");
let plan = make_environment_file_lock_test_plan(&root);
fs::create_dir_all(&plan.env_dir).expect("create published lifecycle test environment");
write_expected_marker(&plan.env_dir, &plan.expected_marker)
.expect("write published lifecycle test marker");
let old_marker = plan.env_dir.join("old-environment");
fs::write(&old_marker, b"old").expect("write old environment marker");
let build_dir = root.join("replacement-build");
fs::create_dir_all(&build_dir).expect("create lifecycle replacement build");
let new_marker_name = "new-environment";
fs::write(build_dir.join(new_marker_name), b"new")
.expect("write replacement environment marker");
let child_executable = std::env::current_exe().expect("resolve lifecycle test executable");
let mut child = Command::new(child_executable)
.arg(
"runtime::managed_runtime::tests::managed_runtime_environment_lifecycle_lease_child_probe",
)
.arg("--exact")
.arg("--nocapture")
.env("LUASKILLS_TEST_ENV_LEASE_ROOT", &root)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("spawn lifecycle lease child probe");
let acquired_path = root.join("lease-acquired");
let acquire_deadline = std::time::Instant::now() + Duration::from_secs(60);
while !acquired_path.exists() && std::time::Instant::now() < acquire_deadline {
thread::sleep(Duration::from_millis(10));
}
if !acquired_path.exists() {
let _ = child.kill();
let _ = child.wait();
panic!("child lifecycle lease probe must acquire its shared lock");
}
let busy_error = finish_build_dir(&plan, build_dir.clone())
.expect_err("active shared lifecycle lease must block environment replacement");
assert!(
busy_error.contains("busy with active lifecycle leases"),
"unexpected lifecycle lease error: {busy_error}"
);
assert!(old_marker.is_file());
assert!(build_dir.join(new_marker_name).is_file());
let release_path = root.join("lease-release");
fs::write(&release_path, b"release").expect("release lifecycle lease child probe");
let exit_deadline = std::time::Instant::now() + Duration::from_secs(10);
let child_status = loop {
if let Some(status) = child.try_wait().expect("poll lifecycle lease child") {
break status;
}
if std::time::Instant::now() >= exit_deadline {
let _ = child.kill();
let _ = child.wait();
panic!("lifecycle lease child did not finish after release");
}
thread::sleep(Duration::from_millis(10));
};
assert!(
child_status.success(),
"lifecycle lease child failed: {child_status}"
);
finish_build_dir(&plan, build_dir)
.expect("environment replacement succeeds after shared lease release");
assert!(plan.env_dir.join(new_marker_name).is_file());
assert!(!plan.env_dir.join("old-environment").exists());
let _ = fs::remove_dir_all(root);
}
#[test]
fn managed_runtime_committed_backup_cleanup_failure_is_retried() {
let root = make_test_root("committed-backup-cleanup-retry");
fs::create_dir_all(&root).expect("create committed backup retry root");
let plan = make_environment_file_lock_test_plan(&root);
fs::create_dir_all(&plan.env_dir).expect("create old managed environment");
fs::write(plan.env_dir.join("old-environment"), b"old")
.expect("write old managed environment evidence");
let build_dir = root.join("replacement-build");
fs::create_dir(&build_dir).expect("create replacement build directory");
fs::write(build_dir.join("new-environment"), b"new")
.expect("write new managed environment evidence");
let captured_backup = Arc::new(Mutex::new(None::<PathBuf>));
let captured_backup_for_remover = Arc::clone(&captured_backup);
let result = finish_build_dir_with_backup_remover(
&plan,
build_dir,
move |backup_path, _identity| {
*captured_backup_for_remover
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) =
Some(backup_path.to_path_buf());
Err("forced obsolete-backup removal failure".to_string())
},
);
assert!(
result.is_ok(),
"committed publication must remain successful"
);
assert!(plan.env_dir.join("new-environment").is_file());
assert!(!plan.env_dir.join("old-environment").exists());
let backup_path = captured_backup
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
.expect("capture committed backup retry path");
let deadline = std::time::Instant::now() + Duration::from_secs(5);
while backup_path.exists() && std::time::Instant::now() < deadline {
thread::sleep(Duration::from_millis(10));
}
assert!(
!backup_path.exists(),
"persistent recovery worker must delete the obsolete backup"
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn managed_runtime_committed_backup_cleanup_panic_is_retried() {
let root = make_test_root("committed-backup-cleanup-panic-retry");
fs::create_dir_all(&root).expect("create committed backup panic retry root");
let plan = make_environment_file_lock_test_plan(&root);
fs::create_dir_all(&plan.env_dir).expect("create old managed environment");
fs::write(plan.env_dir.join("old-environment"), b"old")
.expect("write old managed environment evidence");
let build_dir = root.join("replacement-build");
fs::create_dir(&build_dir).expect("create replacement build directory");
fs::write(build_dir.join("new-environment"), b"new")
.expect("write new managed environment evidence");
let captured_backup = Arc::new(Mutex::new(None::<PathBuf>));
let captured_backup_for_remover = Arc::clone(&captured_backup);
let result = finish_build_dir_with_backup_remover(
&plan,
build_dir,
move |backup_path, _identity| {
*captured_backup_for_remover
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner) =
Some(backup_path.to_path_buf());
panic!("forced obsolete-backup cleanup panic");
},
);
assert!(
result.is_ok(),
"committed publication must survive immediate cleanup panic"
);
assert!(plan.env_dir.join("new-environment").is_file());
assert!(!plan.env_dir.join("old-environment").exists());
let backup_path = captured_backup
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
.expect("capture committed backup panic retry path");
let deadline = std::time::Instant::now() + Duration::from_secs(5);
while backup_path.exists() && std::time::Instant::now() < deadline {
thread::sleep(Duration::from_millis(10));
}
assert!(
!backup_path.exists(),
"persistent recovery worker must delete the backup retained across panic"
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn managed_runtime_failed_publication_backup_is_restored() {
let root = make_test_root("failed-publication-backup-restore");
fs::create_dir_all(&root).expect("create failed publication recovery root");
let plan = make_environment_file_lock_test_plan(&root);
let env_parent = plan.env_dir.parent().expect("managed env parent");
fs::create_dir_all(env_parent).expect("create managed env parent");
let backup_path = env_parent.join(".replaced-restore-test");
fs::create_dir(&backup_path).expect("create failed publication backup");
fs::write(backup_path.join("old-environment"), b"old")
.expect("write failed publication backup evidence");
let identity = capture_managed_directory_identity(
&fs::canonicalize(&backup_path).expect("canonicalize failed publication backup"),
"test failed publication backup",
)
.expect("capture failed publication backup identity");
let canonical_backup = fs::canonicalize(&backup_path)
.expect("retain canonical failed publication backup path");
let (recovery_center, recovery_permit) = reserve_managed_runtime_backup_recovery_slot()
.expect("reserve failed publication recovery slot");
recovery_center.enqueue(ManagedRuntimeBackupRecoveryRecord {
backup_path: canonical_backup,
identity,
action: ManagedRuntimeBackupRecoveryAction::RestoreFailedPublication {
plan: Box::new(plan.clone()),
},
_permit: recovery_permit,
error_reported: false,
});
let deadline = std::time::Instant::now() + Duration::from_secs(5);
while !plan.env_dir.exists() && std::time::Instant::now() < deadline {
thread::sleep(Duration::from_millis(10));
}
assert!(plan.env_dir.join("old-environment").is_file());
assert!(!backup_path.exists());
let _ = fs::remove_dir_all(root);
}
#[test]
fn prepare_build_dir_skips_stale_file_collision() {
let root = make_test_root("file-build-dir");
fs::create_dir_all(&root).expect("create file-backed build directory root");
let managed_runtime_roots = Arc::new(
ManagedRuntimeRoots::new(&root, Some(&root), Some(&root))
.expect("create build-directory test managed runtime roots"),
);
let hash_input = ManagedRuntimeEnvHashInput {
runtime: ManagedRuntimeKind::Python,
runtime_version: "3.14.4".to_string(),
platform: "test-platform".to_string(),
package_manager: "uv".to_string(),
package_manager_version: "0.11.28".to_string(),
lock_hash: "lock".to_string(),
package_manifest_hash: None,
runtime_install_manifest_hash: "python-manifest".to_string(),
runtime_executable_hash: "python-executable".to_string(),
package_manager_install_manifest_hash: "uv-manifest".to_string(),
package_manager_executable_hash: "uv-executable".to_string(),
};
let env_hash = "file-build-hash".to_string();
let expected_marker = ManagedRuntimeEnvMarker::expected(&hash_input, env_hash.clone());
let plan = ManagedRuntimeEnvPlan {
runtime: ManagedRuntimeKind::Python,
platform: hash_input.platform.clone(),
runtime_root: root.clone(),
distribution_root: root.clone(),
environment_root: root.clone(),
distribution_source: ManagedRuntimeRootSource::HostConfigured,
environment_source: ManagedRuntimeRootSource::HostConfigured,
runtime_install_root: root.join("python-install"),
runtime_version: hash_input.runtime_version.clone(),
runtime_executable: PathBuf::from("python"),
runtime_install_manifest_hash: hash_input.runtime_install_manifest_hash.clone(),
runtime_executable_hash: hash_input.runtime_executable_hash.clone(),
package_manager: hash_input.package_manager.clone(),
package_manager_version: hash_input.package_manager_version.clone(),
package_manager_executable: PathBuf::from("uv"),
package_manager_install_root: root.join("uv-install"),
package_manager_install_manifest_hash: hash_input
.package_manager_install_manifest_hash
.clone(),
package_manager_executable_hash: hash_input.package_manager_executable_hash.clone(),
managed_runtime_roots,
package_manifest_path: None,
lockfile_path: root.join("requirements.lock"),
lock_hash: hash_input.lock_hash.clone(),
package_manifest_hash: None,
env_hash: env_hash.clone(),
env_dir: root.join("env"),
expected_marker,
};
let stale_build_path =
root.join(format!(".building-{}-{}-17", env_hash, std::process::id()));
fs::write(&stale_build_path, b"stale build marker")
.expect("stale build file should be written");
let mut sequences = [17_u64, 18_u64].into_iter();
let build_dir = prepare_build_dir_with_sequence(&plan, || {
sequences
.next()
.ok_or_else(|| "test build sequence exhausted".to_string())
})
.expect("stale build file collision should be skipped");
let expected_build_dir =
root.join(format!(".building-{}-{}-18", env_hash, std::process::id()));
assert_eq!(build_dir, expected_build_dir);
assert!(build_dir.is_dir());
assert_eq!(
fs::read(&stale_build_path).expect("read preserved stale build file"),
b"stale build marker"
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn copy_dir_recursive_rejects_symlink_entry() {
let root = make_test_root("copy-symlink-entry");
let source_dir = root.join("source");
let destination_dir = root.join("destination");
fs::create_dir_all(&source_dir).expect("copy source dir should be created");
let real_file_path = root.join("real-handler.py");
fs::write(&real_file_path, b"print('ok')").expect("real file should be written");
let symlink_path = source_dir.join("handler-link.py");
if !create_test_file_symlink(&symlink_path, &real_file_path) {
let _ = fs::remove_dir_all(root);
return;
}
let error = copy_dir_recursive(&source_dir, &destination_dir)
.expect_err("symlink entry should fail managed runtime copy");
assert!(
error.contains("unsupported file type"),
"unexpected error: {}",
error
);
assert!(
error.contains(&render_host_visible_path(&symlink_path)),
"unexpected error: {}",
error
);
let _ = fs::remove_dir_all(root);
}
#[cfg(unix)]
#[test]
fn copy_dir_recursive_rejects_unsupported_unix_file_type() {
use std::os::unix::fs::FileTypeExt;
let root = make_test_root("copy-unsupported-type");
let source_dir = root.join("source");
let destination_dir = root.join("destination");
fs::create_dir_all(&source_dir).expect("copy source dir should be created");
let fifo_path = source_dir.join("events.pipe");
let status = std::process::Command::new("mkfifo")
.arg(&fifo_path)
.status()
.expect("mkfifo should run");
assert!(status.success(), "mkfifo should create FIFO fixture");
assert!(
fs::metadata(&fifo_path)
.expect("FIFO metadata should be readable")
.file_type()
.is_fifo(),
"fixture should be FIFO"
);
let error = copy_dir_recursive(&source_dir, &destination_dir)
.expect_err("unsupported FIFO entry should fail managed runtime copy");
assert!(
error.contains("unsupported file type"),
"unexpected error: {}",
error
);
assert!(
error.contains(&render_host_visible_path(&fifo_path)),
"unexpected error: {}",
error
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn python_env_plan_resolves_manifests_and_lockfile() {
let platform = current_managed_runtime_platform_key().expect("platform should resolve");
let root = make_test_root("python-plan");
let runtime_root = root.join("runtime");
let package_root = root.join("skill");
fs::create_dir_all(package_root.join("python")).unwrap();
fs::write(
package_root.join("python/requirements.lock"),
b"requests==2.32.3",
)
.unwrap();
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("cpython-3.14.4-{}", platform)),
"python",
"3.14.4",
&platform,
platform_executable("python"),
);
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("uv-0.11.28-{}", platform)),
"uv",
"0.11.28",
&platform,
platform_executable("uv"),
);
let package = make_test_package_context(&package_root, &runtime_root);
let plan = resolve_python_env_plan(
package.as_ref(),
&PythonRuntimeDependencySpec {
version: "3.14.4".to_string(),
package_manager: PythonRuntimePackageManager::Uv,
package_manager_version: "0.11.28".to_string(),
lockfile: "python/requirements.lock".to_string(),
required: true,
},
)
.expect("python env plan should resolve");
assert_eq!(plan.runtime, ManagedRuntimeKind::Python);
assert_eq!(plan.package_manager, "uv");
assert_eq!(plan.lock_hash, sha256_hex(b"requests==2.32.3"));
assert_eq!(plan.expected_marker.env_hash, plan.env_hash);
assert!(
plan.env_dir
.starts_with(package.runtime_root().join("dependencies/envs/python"))
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn python_env_plan_rejects_directory_runtime_executable() {
let platform = current_managed_runtime_platform_key().expect("platform should resolve");
let root = make_test_root("directory-runtime-executable");
let runtime_root = root.join("runtime");
let package_root = root.join("skill");
fs::create_dir_all(package_root.join("python")).unwrap();
fs::write(
package_root.join("python/requirements.lock"),
b"requests==2.32.3",
)
.unwrap();
let python_install_dir = runtime_root
.join("dependencies")
.join("runtimes")
.join("python")
.join(format!("cpython-3.14.4-{}", platform));
let executable_path = python_install_dir.join(platform_executable("python"));
fs::create_dir_all(&executable_path).expect("directory executable should be created");
let payload = serde_json::json!({
"schema_version": 1,
"runtime": "python",
"version": "3.14.4",
"platform": platform,
"executable": platform_executable("python"),
});
fs::write(
python_install_dir.join("runtime-manifest.json"),
serde_json::to_string_pretty(&payload).unwrap(),
)
.unwrap();
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("uv-0.11.28-{}", platform)),
"uv",
"0.11.28",
&platform,
platform_executable("uv"),
);
let package = make_test_package_context(&package_root, &runtime_root);
let error = resolve_python_env_plan(
package.as_ref(),
&PythonRuntimeDependencySpec {
version: "3.14.4".to_string(),
package_manager: PythonRuntimePackageManager::Uv,
package_manager_version: "0.11.28".to_string(),
lockfile: "python/requirements.lock".to_string(),
required: true,
},
)
.expect_err("directory runtime executable should be rejected");
let canonical_executable_path = fs::canonicalize(&executable_path)
.expect("canonicalize directory runtime executable fixture");
let expected_error = format!(
"managed runtime distribution source=runtime_root_default: managed runtime executable is not a file: {}",
render_host_visible_path(&canonical_executable_path)
);
assert_eq!(error, expected_error);
let _ = fs::remove_dir_all(root);
}
#[test]
fn node_env_plan_includes_package_manifest_hash() {
let platform = current_managed_runtime_platform_key().expect("platform should resolve");
let root = make_test_root("node-plan");
let runtime_root = root.join("runtime");
let package_root = root.join("skill");
fs::create_dir_all(package_root.join("node")).unwrap();
fs::write(
package_root.join("node/package.json"),
br#"{"dependencies":{}}"#,
)
.unwrap();
fs::write(
package_root.join("node/pnpm-lock.yaml"),
b"lockfileVersion: '9.0'",
)
.unwrap();
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/node")
.join(format!("node-24.18.0-{}", platform)),
"node",
"24.18.0",
&platform,
platform_executable("node"),
);
write_install_manifest(
&runtime_root.join("dependencies/runtimes/node/pnpm-11.11.0"),
"pnpm",
"11.11.0",
"any",
"bin/pnpm.cjs",
);
let package = make_test_package_context(&package_root, &runtime_root);
let plan = resolve_node_env_plan(
package.as_ref(),
&NodeRuntimeDependencySpec {
version: "24.18.0".to_string(),
package_manager: NodeRuntimePackageManager::Pnpm,
package_manager_version: "11.11.0".to_string(),
package_json: "node/package.json".to_string(),
lockfile: "node/pnpm-lock.yaml".to_string(),
required: true,
},
)
.expect("node env plan should resolve");
assert_eq!(plan.runtime, ManagedRuntimeKind::Node);
assert_eq!(plan.package_manager, "pnpm");
assert_eq!(plan.lock_hash, sha256_hex(b"lockfileVersion: '9.0'"));
assert_eq!(
plan.package_manifest_hash,
Some(sha256_hex(br#"{"dependencies":{}}"#))
);
assert!(
plan.env_dir
.starts_with(package.runtime_root().join("dependencies/envs/node"))
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn node_env_plan_rejects_runtime_older_than_supported_esm_baseline() {
let root = make_test_root("unsupported-node-runtime-version");
let runtime_root = root.join("runtime");
let package_root = root.join("skill");
let package = make_test_package_context(&package_root, &runtime_root);
let error = resolve_node_env_plan(
package.as_ref(),
&NodeRuntimeDependencySpec {
version: "18.19.1".to_string(),
package_manager: NodeRuntimePackageManager::Pnpm,
package_manager_version: "11.11.0".to_string(),
package_json: "node/package.json".to_string(),
lockfile: "node/pnpm-lock.yaml".to_string(),
required: true,
},
)
.expect_err("pre-22 Node runtime must be rejected before installation lookup");
assert_eq!(
error,
"managed Node runtime 18.19.1 is unsupported; minimum supported version is 22.0.0"
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn node_env_plan_rejects_non_semantic_runtime_version() {
let root = make_test_root("invalid-node-runtime-version");
let runtime_root = root.join("runtime");
let package_root = root.join("skill");
let package = make_test_package_context(&package_root, &runtime_root);
let error = resolve_node_env_plan(
package.as_ref(),
&NodeRuntimeDependencySpec {
version: "current".to_string(),
package_manager: NodeRuntimePackageManager::Pnpm,
package_manager_version: "11.11.0".to_string(),
package_json: "node/package.json".to_string(),
lockfile: "node/pnpm-lock.yaml".to_string(),
required: true,
},
)
.expect_err("non-semantic Node runtime version must be rejected");
assert!(error.starts_with("node_runtime.version is not valid semantic version 'current':"));
let _ = fs::remove_dir_all(root);
}
#[test]
fn managed_runtime_plan_rejects_package_path_traversal() {
let platform = current_managed_runtime_platform_key().expect("platform should resolve");
let root = make_test_root("path-traversal");
let runtime_root = root.join("runtime");
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("cpython-3.14.4-{}", platform)),
"python",
"3.14.4",
&platform,
platform_executable("python"),
);
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("uv-0.11.28-{}", platform)),
"uv",
"0.11.28",
&platform,
platform_executable("uv"),
);
let package_root = root.join("skill");
let package = make_test_package_context(&package_root, &runtime_root);
let error = resolve_python_env_plan(
package.as_ref(),
&PythonRuntimeDependencySpec {
version: "3.14.4".to_string(),
package_manager: PythonRuntimePackageManager::Uv,
package_manager_version: "0.11.28".to_string(),
lockfile: "../outside.lock".to_string(),
required: true,
},
)
.expect_err("path traversal should be rejected");
assert!(error.contains("safe path under the package root"));
let _ = fs::remove_dir_all(root);
}
#[test]
fn python_env_plan_missing_lockfile_error_uses_host_visible_path() {
let platform = current_managed_runtime_platform_key().expect("platform should resolve");
let root = make_test_root("missing-lockfile-path");
let runtime_root = root.join("runtime");
let package_root = root.join("skill");
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("cpython-3.14.4-{}", platform)),
"python",
"3.14.4",
&platform,
platform_executable("python"),
);
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("uv-0.11.28-{}", platform)),
"uv",
"0.11.28",
&platform,
platform_executable("uv"),
);
let package = make_test_package_context(&package_root, &runtime_root);
let expected_lockfile = package.package_root().join("python").join("missing.lock");
let error = resolve_python_env_plan(
package.as_ref(),
&PythonRuntimeDependencySpec {
version: "3.14.4".to_string(),
package_manager: PythonRuntimePackageManager::Uv,
package_manager_version: "0.11.28".to_string(),
lockfile: "python/missing.lock".to_string(),
required: true,
},
)
.expect_err("missing lockfile should be reported");
assert!(
error.contains("failed to canonicalize python_runtime.lockfile"),
"unexpected error: {error}"
);
assert!(
error.contains(&render_host_visible_path(&expected_lockfile)),
"unexpected error: {error}"
);
let _ = fs::remove_dir_all(root);
}
#[test]
fn python_env_plan_rejects_directory_lockfile() {
let platform = current_managed_runtime_platform_key().expect("platform should resolve");
let root = make_test_root("directory-lockfile");
let runtime_root = root.join("runtime");
let package_root = root.join("skill");
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("cpython-3.14.4-{}", platform)),
"python",
"3.14.4",
&platform,
platform_executable("python"),
);
write_install_manifest(
&runtime_root
.join("dependencies/runtimes/python")
.join(format!("uv-0.11.28-{}", platform)),
"uv",
"0.11.28",
&platform,
platform_executable("uv"),
);
let lockfile_path = package_root.join("python/requirements.lock");
fs::create_dir_all(&lockfile_path).expect("directory lockfile should be created");
let package = make_test_package_context(&package_root, &runtime_root);
let error = resolve_python_env_plan(
package.as_ref(),
&PythonRuntimeDependencySpec {
version: "3.14.4".to_string(),
package_manager: PythonRuntimePackageManager::Uv,
package_manager_version: "0.11.28".to_string(),
lockfile: "python/requirements.lock".to_string(),
required: true,
},
)
.expect_err("directory lockfile should be rejected");
let canonical_lockfile_path =
fs::canonicalize(&lockfile_path).expect("canonicalize directory lockfile fixture");
let expected_error = format!(
"python_runtime.lockfile is not a file: {}",
render_host_visible_path(&canonical_lockfile_path)
);
assert_eq!(error, expected_error);
let _ = fs::remove_dir_all(root);
}
#[test]
fn explicit_host_roots_drive_python_plan_and_asset_invalidation() {
let root = make_test_root("explicit-host-roots");
let runtime_root = root.join("运行时 数据");
let distribution_root = root.join("应用 资产").join("runtimes");
let environment_root = root.join("用户 数据").join("managed runtime envs");
let package_root = root.join("插件 包");
fs::create_dir_all(&runtime_root).expect("create explicit data root");
fs::create_dir_all(package_root.join("python")).expect("create explicit package root");
fs::write(
package_root.join("python/requirements.lock"),
b"requests==2.32.3",
)
.expect("write explicit Python lockfile");
let platform = current_managed_runtime_platform_key().expect("resolve native platform");
let runtime_install_root = distribution_root
.join("python")
.join(format!("cpython-3.14.4-{platform}"));
let package_manager_install_root = distribution_root
.join("python")
.join(format!("uv-0.11.28-{platform}"));
write_install_manifest(
&runtime_install_root,
"python",
"3.14.4",
&platform,
platform_executable("python"),
);
write_install_manifest(
&package_manager_install_root,
"uv",
"0.11.28",
&platform,
platform_executable("uv"),
);
let managed_runtime_roots = Arc::new(
ManagedRuntimeRoots::new(
&runtime_root,
Some(&distribution_root),
Some(&environment_root),
)
.expect("build explicit managed runtime roots"),
);
let package = ManagedRuntimePackageContext::for_skill_with_roots(
"explicit-host-roots",
&package_root,
Arc::clone(&managed_runtime_roots),
None,
)
.expect("build explicit-root package context");
let spec = PythonRuntimeDependencySpec {
version: "3.14.4".to_string(),
package_manager: PythonRuntimePackageManager::Uv,
package_manager_version: "0.11.28".to_string(),
lockfile: "python/requirements.lock".to_string(),
required: true,
};
let original_plan =
resolve_python_env_plan(package.as_ref(), &spec).expect("resolve explicit Python plan");
assert_eq!(
original_plan.distribution_root,
fs::canonicalize(&distribution_root).expect("canonical explicit distribution root")
);
assert_eq!(
original_plan.environment_root,
fs::canonicalize(&environment_root).expect("canonical explicit environment root")
);
assert_eq!(
original_plan.distribution_source,
ManagedRuntimeRootSource::HostConfigured
);
assert_eq!(
original_plan.environment_source,
ManagedRuntimeRootSource::HostConfigured
);
assert!(
original_plan
.runtime_executable
.starts_with(&original_plan.distribution_root)
);
assert!(
original_plan
.env_dir
.starts_with(original_plan.environment_root.join("python"))
);
fs::write(
&original_plan.runtime_executable,
b"changed executable bytes",
)
.expect("mutate explicit Python executable");
let changed_plan =
resolve_python_env_plan(package.as_ref(), &spec).expect("resolve changed Python plan");
assert_ne!(
original_plan.runtime_executable_hash,
changed_plan.runtime_executable_hash
);
assert_ne!(original_plan.env_hash, changed_plan.env_hash);
let identity_error = validate_managed_runtime_plan_assets(&original_plan)
.expect_err("stale plan must reject changed executable identity");
assert!(identity_error.contains("installation identity changed"));
let runtime_manifest_path = changed_plan
.runtime_install_root
.join("runtime-manifest.json");
let repacked_manifest = serde_json::json!({
"schema_version": 1,
"runtime": "python",
"version": "3.14.4",
"platform": platform,
"executable": platform_executable("python"),
"source": "repacked-test-asset",
});
fs::write(
&runtime_manifest_path,
serde_json::to_string_pretty(&repacked_manifest)
.expect("serialize repacked Python runtime manifest"),
)
.expect("replace explicit Python runtime manifest bytes");
let manifest_changed_plan = resolve_python_env_plan(package.as_ref(), &spec)
.expect("resolve manifest-changed Python plan");
assert_ne!(
changed_plan.runtime_install_manifest_hash,
manifest_changed_plan.runtime_install_manifest_hash
);
assert_ne!(changed_plan.env_hash, manifest_changed_plan.env_hash);
let manifest_identity_error = validate_managed_runtime_plan_assets(&changed_plan)
.expect_err("stale plan must reject changed manifest identity");
assert!(manifest_identity_error.contains("installation identity changed"));
let _ = fs::remove_dir_all(root);
}
#[test]
fn explicit_host_roots_drive_node_plan() {
let root = make_test_root("explicit-node-host-roots");
let runtime_root = root.join("runtime data");
let distribution_root = root.join("application assets").join("runtimes");
let environment_root = root.join("user data").join("managed environments");
let package_root = root.join("system plugin");
fs::create_dir_all(&runtime_root).expect("create explicit Node data root");
fs::create_dir_all(package_root.join("node")).expect("create explicit Node package root");
fs::write(
package_root.join("node/package.json"),
b"{\"private\":true}",
)
.expect("write explicit Node package manifest");
fs::write(
package_root.join("node/pnpm-lock.yaml"),
b"lockfileVersion: '9.0'",
)
.expect("write explicit Node lockfile");
let platform = current_managed_runtime_platform_key().expect("resolve native platform");
let runtime_install_root = distribution_root
.join("node")
.join(format!("node-24.18.0-{platform}"));
let package_manager_install_root = distribution_root.join("node").join("pnpm-11.11.0");
write_install_manifest(
&runtime_install_root,
"node",
"24.18.0",
&platform,
platform_executable("node"),
);
write_install_manifest(
&package_manager_install_root,
"pnpm",
"11.11.0",
"any",
"bin/pnpm.cjs",
);
let managed_runtime_roots = Arc::new(
ManagedRuntimeRoots::new(
&runtime_root,
Some(&distribution_root),
Some(&environment_root),
)
.expect("build explicit Node managed runtime roots"),
);
let package = ManagedRuntimePackageContext::for_skill_with_roots(
"explicit-node-host-roots",
&package_root,
Arc::clone(&managed_runtime_roots),
None,
)
.expect("build explicit Node package context");
let spec = NodeRuntimeDependencySpec {
version: "24.18.0".to_string(),
package_manager: NodeRuntimePackageManager::Pnpm,
package_manager_version: "11.11.0".to_string(),
package_json: "node/package.json".to_string(),
lockfile: "node/pnpm-lock.yaml".to_string(),
required: true,
};
let plan =
resolve_node_env_plan(package.as_ref(), &spec).expect("resolve explicit Node plan");
assert_eq!(
plan.distribution_root,
fs::canonicalize(&distribution_root).expect("canonical explicit Node distribution")
);
assert_eq!(
plan.environment_root,
fs::canonicalize(&environment_root).expect("canonical explicit Node environment")
);
assert_eq!(
plan.distribution_source,
ManagedRuntimeRootSource::HostConfigured
);
assert_eq!(
plan.environment_source,
ManagedRuntimeRootSource::HostConfigured
);
assert!(plan.runtime_executable.starts_with(&plan.distribution_root));
assert!(plan.env_dir.starts_with(plan.environment_root.join("node")));
let _ = fs::remove_dir_all(root);
}
#[test]
fn public_managed_runtime_resolver_returns_canonical_descriptor() {
let root = make_test_root("public-install-resolver");
let distribution_root = root.join("shared runtimes");
let install_root = distribution_root
.join("node")
.join("node-24.18.0-linux-x64");
write_install_manifest(&install_root, "node", "24.18.0", "linux-x64", "bin/node");
let descriptor = resolve_managed_runtime_install(
&distribution_root,
ManagedRuntimeKind::Node,
"24.18.0",
"linux-x64",
)
.expect("resolve public Node descriptor");
assert_eq!(descriptor.runtime, ManagedRuntimeKind::Node);
assert_eq!(
descriptor.install_root,
fs::canonicalize(&install_root).expect("canonical public install root")
);
assert_eq!(
descriptor.manifest_hash,
sha256_file(&install_root.join("runtime-manifest.json")).expect("hash public manifest")
);
assert_eq!(
descriptor.executable_hash,
sha256_file(&descriptor.executable).expect("hash public executable")
);
assert!(descriptor.executable.starts_with(&descriptor.install_root));
let _ = fs::remove_dir_all(root);
}
#[test]
fn managed_runtime_roots_reject_invalid_and_replaced_distribution_roots() {
let root = make_test_root("root-validation");
let runtime_root = root.join("runtime");
let distribution_root = root.join("distribution");
let environment_root = root.join("environment");
fs::create_dir_all(&runtime_root).expect("create root-validation data root");
fs::create_dir_all(&distribution_root).expect("create root-validation distribution root");
let relative_error = ManagedRuntimeRoots::new(
&runtime_root,
Some(Path::new("relative-runtimes")),
Some(&environment_root),
)
.expect_err("relative distribution root must fail");
assert!(relative_error.contains("must be an absolute path"));
let relative_environment_error = ManagedRuntimeRoots::new(
&runtime_root,
Some(&distribution_root),
Some(Path::new("relative-environments")),
)
.expect_err("relative environment root must fail");
assert!(relative_environment_error.contains("must be an absolute path"));
let missing_error = ManagedRuntimeRoots::new(
&runtime_root,
Some(&root.join("missing-distribution")),
Some(&environment_root),
)
.expect_err("missing explicit distribution root must fail");
assert!(missing_error.contains("failed to canonicalize"));
let distribution_file = root.join("distribution-file");
fs::write(&distribution_file, b"not a directory").expect("write distribution file fixture");
let distribution_file_error = ManagedRuntimeRoots::new(
&runtime_root,
Some(&distribution_file),
Some(&environment_root),
)
.expect_err("distribution file must fail");
assert!(distribution_file_error.contains("is not a directory"));
let environment_file = root.join("environment-file");
fs::write(&environment_file, b"not a directory").expect("write environment file fixture");
let environment_file_error = ManagedRuntimeRoots::new(
&runtime_root,
Some(&distribution_root),
Some(&environment_file),
)
.expect_err("environment file must fail");
assert!(
environment_file_error.contains("failed to create managed runtime environment root")
);
let roots = ManagedRuntimeRoots::new(
&runtime_root,
Some(&distribution_root),
Some(&environment_root),
)
.expect("build replace-detection roots");
let original_distribution = root.join("original-distribution");
fs::rename(&distribution_root, &original_distribution)
.expect("move original distribution root");
fs::create_dir(&distribution_root).expect("create replacement distribution root");
let replacement_error = roots
.validate_live_filesystem_identity()
.expect_err("same-path distribution replacement must fail");
assert!(replacement_error.contains("filesystem object changed"));
let _ = fs::remove_dir_all(root);
}
#[test]
fn managed_runtime_roots_reject_replaced_environment_root() {
let root = make_test_root("environment-root-replacement");
let runtime_root = root.join("runtime");
let distribution_root = root.join("distribution");
let environment_root = root.join("environment");
fs::create_dir_all(&runtime_root).expect("create environment replacement runtime root");
fs::create_dir_all(&distribution_root)
.expect("create environment replacement distribution root");
let roots = ManagedRuntimeRoots::new(
&runtime_root,
Some(&distribution_root),
Some(&environment_root),
)
.expect("build environment replace-detection roots");
let original_environment = root.join("original-environment");
fs::rename(&environment_root, &original_environment)
.expect("move original environment root");
fs::create_dir(&environment_root).expect("create replacement environment root");
let replacement_error = roots
.validate_live_filesystem_identity()
.expect_err("same-path environment replacement must fail");
assert!(replacement_error.contains("filesystem object changed"));
let _ = fs::remove_dir_all(root);
}
fn make_test_root(label: &str) -> PathBuf {
let root = std::env::temp_dir().join(format!(
"luaskills-managed-runtime-{}-{}-{}",
label,
std::process::id(),
unique_suffix()
));
fs::create_dir_all(&root).unwrap();
root
}
fn unique_suffix() -> u128 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
}
fn write_install_manifest(
install_dir: &Path,
runtime: &str,
version: &str,
platform: &str,
executable: &str,
) {
fs::create_dir_all(
install_dir.join(Path::new(executable).parent().unwrap_or(Path::new(""))),
)
.unwrap();
fs::write(install_dir.join(executable), b"executable").unwrap();
let payload = serde_json::json!({
"schema_version": 1,
"runtime": runtime,
"version": version,
"platform": platform,
"executable": executable,
});
fs::write(
install_dir.join("runtime-manifest.json"),
serde_json::to_string_pretty(&payload).unwrap(),
)
.unwrap();
}
fn platform_executable(kind: &str) -> &'static str {
match (std::env::consts::OS, kind) {
("windows", "python") => "python.exe",
("windows", "uv") => "uv.exe",
("windows", "node") => "node.exe",
(_, "python") => "bin/python3",
(_, "uv") => "uv",
(_, "node") => "bin/node",
_ => "tool",
}
}
}