#[cfg(windows)]
use super::runlua::has_invalid_windows_path_syntax;
use super::runlua::{
default_runlua_timeout_ms, lock_runlua_cwd_guard, resolve_host_default_text_encoding,
};
use super::*;
use crate::runtime::managed_package::{
ManagedFilesystemObjectIdentity, ManagedRuntimeLeaseBinding,
capture_managed_directory_identity, validate_lua_search_root_path,
validate_managed_directory_identity,
};
use mlua::{MetaMethod, UserData, UserDataMethods};
#[derive(Debug, Deserialize)]
struct RuntimeSessionCreateRequest {
sid: String,
#[serde(default)]
ttl_sec: Option<u64>,
#[serde(default)]
replace: bool,
#[serde(default)]
cwd: Option<String>,
#[serde(default)]
workspace_root: Option<String>,
#[serde(default)]
lua_roots: Vec<String>,
#[serde(default)]
c_roots: Vec<String>,
#[serde(default = "default_runlua_exec_args")]
mounts: Value,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct SystemRuntimePackageRequest {
id: String,
root: String,
dependencies_file: String,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct SystemRuntimeSessionCreateRequest {
sid: String,
#[serde(default)]
ttl_sec: Option<u64>,
#[serde(default)]
replace: bool,
#[serde(default)]
cwd: Option<String>,
#[serde(default)]
workspace_root: Option<String>,
#[serde(default = "default_runlua_exec_args")]
mounts: Value,
system_package: SystemRuntimePackageRequest,
}
#[derive(Debug, Deserialize)]
struct RuntimeSessionEvalRequest {
lease_id: String,
#[serde(default)]
sid: Option<String>,
#[serde(default)]
generation: Option<u64>,
code: String,
#[serde(default = "default_runlua_exec_args")]
args: Value,
#[serde(default = "default_runlua_timeout_ms")]
timeout_ms: u64,
#[serde(default)]
request_context: Option<RuntimeRequestContext>,
#[serde(default = "default_runlua_exec_args")]
client_budget: Value,
#[serde(default = "default_runlua_exec_args")]
tool_config: Value,
}
#[derive(Debug, Deserialize)]
struct RuntimeSessionLeaseRequest {
lease_id: String,
#[serde(default)]
sid: Option<String>,
#[serde(default)]
generation: Option<u64>,
}
#[derive(Debug, Deserialize)]
struct RuntimeSessionListRequest {
#[serde(default)]
sid: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum RuntimeLeaseProfile {
Public,
SystemLuaLib,
}
impl RuntimeLeaseProfile {
fn as_str(self) -> &'static str {
match self {
Self::Public => "public",
Self::SystemLuaLib => "system_lua_lib",
}
}
}
#[derive(Debug, Clone)]
struct RuntimeLeasePathContext {
cwd: Option<PathBuf>,
cwd_identity: Option<ManagedFilesystemObjectIdentity>,
workspace_root: Option<PathBuf>,
workspace_root_identity: Option<ManagedFilesystemObjectIdentity>,
lua_roots: Vec<PathBuf>,
c_roots: Vec<PathBuf>,
mounts: Value,
system_lua_lib_dir: Option<PathBuf>,
managed_package: Option<Arc<ManagedRuntimePackageContext>>,
}
impl RuntimeLeasePathContext {
fn validate_live_system_directory_identities(&self) -> Result<(), String> {
if let Some(identity) = self.workspace_root_identity.as_ref() {
let workspace_root = self.workspace_root.as_deref().ok_or_else(|| {
"system runtime workspace_root identity has no canonical path".to_string()
})?;
validate_managed_directory_identity(workspace_root, identity, "workspace_root")?;
}
if let Some(identity) = self.cwd_identity.as_ref() {
let cwd = self
.cwd
.as_deref()
.ok_or_else(|| "system runtime cwd identity has no canonical path".to_string())?;
validate_managed_directory_identity(cwd, identity, "cwd")?;
}
Ok(())
}
}
pub(super) struct RuntimeSessionManager {
state: Mutex<RuntimeSessionManagerState>,
}
struct RuntimeSessionManagerState {
leases: HashMap<String, RuntimeSessionEntry>,
sid_index: HashMap<String, String>,
tombstones: HashMap<String, RuntimeSessionTombstone>,
generations: HashMap<String, u64>,
next_sequence: u64,
retired_owner_tokens: Vec<u64>,
}
pub(super) struct RuntimeSession {
sid: String,
lease_id: String,
generation: u64,
profile: RuntimeLeaseProfile,
ttl_sec: Option<u64>,
expires_at: Option<Instant>,
expires_at_unix_ms: Option<u128>,
path_context: RuntimeLeasePathContext,
vm: LuaVm,
terminal_state: Arc<AtomicU8>,
closed: bool,
}
struct RuntimeSessionEntry {
session: Arc<Mutex<RuntimeSession>>,
sid: String,
lease_id: String,
generation: u64,
profile: RuntimeLeaseProfile,
owner_token: Option<u64>,
terminal_state: Arc<AtomicU8>,
snapshot: Value,
}
impl RuntimeSessionEntry {
fn list_payload(&self) -> Result<Value, RuntimeSessionError> {
let mut snapshot = self.snapshot.clone();
let Some(object) = snapshot.as_object_mut() else {
return Err(RuntimeSessionError {
code: "lease_snapshot_corrupted",
message: format!(
"runtime session lease `{}` active snapshot is not a JSON object",
self.lease_id
),
});
};
object.insert("sid".to_string(), Value::String(self.sid.clone()));
object.insert("lease_id".to_string(), Value::String(self.lease_id.clone()));
object.insert("generation".to_string(), json!(self.generation));
object.insert(
"profile".to_string(),
Value::String(self.profile.as_str().to_string()),
);
Ok(snapshot)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
enum RuntimeSessionTerminalState {
Active = 0,
Closed = 1,
Expired = 2,
Replaced = 3,
}
#[derive(Debug)]
pub(super) struct RuntimeSessionError {
pub(super) code: &'static str,
pub(super) message: String,
}
struct RuntimeSessionTombstone {
sid: String,
lease_id: String,
generation: u64,
profile: RuntimeLeaseProfile,
code: &'static str,
retired_at: Instant,
}
pub(super) fn default_runlua_exec_args() -> Value {
Value::Object(serde_json::Map::new())
}
fn default_runtime_session_ttl_sec() -> u64 {
600
}
impl RuntimeSessionEvalRequest {
fn to_invocation_context(&self) -> LuaInvocationContext {
LuaInvocationContext::new(
self.request_context.clone(),
self.client_budget.clone(),
self.tool_config.clone(),
)
}
}
fn next_runtime_session_sequence(current: u64) -> Result<u64, RuntimeSessionError> {
current.checked_add(1).ok_or_else(|| RuntimeSessionError {
code: "lease_sequence_exhausted",
message: "runtime session lease sequence is exhausted".to_string(),
})
}
fn next_runtime_session_generation(current: u64, sid: &str) -> Result<u64, RuntimeSessionError> {
current.checked_add(1).ok_or_else(|| RuntimeSessionError {
code: "lease_generation_exhausted",
message: format!("runtime session SID `{sid}` generation is exhausted"),
})
}
impl RuntimeSessionManager {
pub(super) fn new() -> Self {
Self {
state: Mutex::new(RuntimeSessionManagerState {
leases: HashMap::new(),
sid_index: HashMap::new(),
tombstones: HashMap::new(),
generations: HashMap::new(),
next_sequence: 0,
retired_owner_tokens: Vec::new(),
}),
}
}
fn insert(
&self,
profile: RuntimeLeaseProfile,
sid: String,
ttl_sec: Option<u64>,
replace: bool,
path_context: RuntimeLeasePathContext,
vm: LuaVm,
) -> Result<Value, RuntimeSessionError> {
let mut pending_path_context = Some(path_context);
let mut pending_vm = Some(vm);
let mut retired_entries = Vec::new();
let mut state = self.lock_state()?;
retired_entries.extend(Self::prune_inactive_locked(&mut state));
let result = (|| {
let mut replacement_lease_id = None;
if let Some(existing_lease_id) = state.sid_index.get(&sid).cloned() {
if let Some(existing_session) = state
.leases
.get(&existing_lease_id)
.map(|entry| Arc::clone(&entry.session))
{
let existing_session = match existing_session.try_lock() {
Ok(existing_session) => existing_session,
Err(TryLockError::WouldBlock) => {
return Err(RuntimeSessionError {
code: if replace {
"lease_busy"
} else {
"lease_exists"
},
message: if replace {
format!(
"runtime session SID `{sid}` cannot replace busy lease `{existing_lease_id}`"
)
} else {
format!(
"runtime session SID `{sid}` already has lease `{existing_lease_id}`"
)
},
});
}
Err(TryLockError::Poisoned(poisoned)) => poisoned.into_inner(),
};
if let Some(error) = existing_session.inactive_error() {
drop(existing_session);
if let Some(entry) = Self::retire_active_lease_locked(
&mut state,
&existing_lease_id,
error.code,
) {
retired_entries.push(entry);
}
} else if !replace {
return Err(RuntimeSessionError {
code: "lease_exists",
message: format!(
"runtime session SID `{sid}` already has lease `{existing_lease_id}`"
),
});
} else {
replacement_lease_id = Some(existing_lease_id);
}
} else {
state.sid_index.remove(&sid);
}
}
if state.leases.len() >= 8 && replacement_lease_id.is_none() {
return Err(RuntimeSessionError {
code: "lease_limit_exceeded",
message: "runtime session lease limit exceeded".to_string(),
});
}
let next_sequence = next_runtime_session_sequence(state.next_sequence)?;
let generation = next_runtime_session_generation(
state.generations.get(&sid).copied().unwrap_or(0),
&sid,
)?;
let lease_id = format!("rt_{}_{}", unix_time_millis(), next_sequence);
let path_context = pending_path_context
.as_ref()
.expect("pending runtime lease path context must exist before commit");
if profile == RuntimeLeaseProfile::SystemLuaLib {
let package =
path_context
.managed_package
.as_ref()
.ok_or_else(|| RuntimeSessionError {
code: "system_package_missing",
message: "system runtime lease is missing its trusted package context"
.to_string(),
})?;
let binding = package.lease_binding().ok_or_else(|| RuntimeSessionError {
code: "system_package_binding_missing",
message: "system runtime package is missing its lease binding".to_string(),
})?;
binding
.bind(lease_id.clone(), generation)
.map_err(|message| RuntimeSessionError {
code: "system_package_binding_failed",
message,
})?;
}
let owner_token = path_context
.managed_package
.as_ref()
.map(|package| package.owner_token());
let ttl_sec = ttl_sec.map(|value| value.clamp(1, 3_600));
let (expires_at, expires_at_unix_ms) = runtime_session_expiry(ttl_sec);
let terminal_state = Arc::new(AtomicU8::new(RuntimeSessionTerminalState::Active as u8));
let path_context = pending_path_context
.take()
.expect("pending runtime lease path context must exist at commit");
let vm = pending_vm
.take()
.expect("pending runtime lease VM must exist at commit");
let session = RuntimeSession {
sid: sid.clone(),
lease_id: lease_id.clone(),
generation,
profile,
ttl_sec,
expires_at,
expires_at_unix_ms,
path_context,
vm,
terminal_state: Arc::clone(&terminal_state),
closed: false,
};
let snapshot = session.status_payload();
let response_snapshot = snapshot.clone();
let new_entry = RuntimeSessionEntry {
session: Arc::new(Mutex::new(session)),
sid: sid.clone(),
lease_id: lease_id.clone(),
generation,
profile,
owner_token,
terminal_state,
snapshot,
};
state.next_sequence = next_sequence;
state.generations.insert(sid.clone(), generation);
if let Some(existing_lease_id) = replacement_lease_id {
let retired_entry = Self::retire_active_lease_locked(
&mut state,
&existing_lease_id,
"lease_replaced",
)
.expect("validated replacement lease must remain present during locked commit");
retired_entries.push(retired_entry);
}
state.leases.insert(lease_id.clone(), new_entry);
state.sid_index.insert(sid.clone(), lease_id.clone());
Ok(json!({
"ok": true,
"sid": sid,
"lease_id": lease_id,
"generation": generation,
"profile": profile.as_str(),
"lifetime": if ttl_sec.is_some() { "finite" } else { "infinite" },
"cwd": response_snapshot.get("cwd").cloned().unwrap_or(Value::Null),
"workspace_root": response_snapshot
.get("workspace_root")
.cloned()
.unwrap_or(Value::Null),
"system_lua_lib": response_snapshot.get("system_lua_lib").cloned().unwrap_or(Value::Null),
"system_package": response_snapshot.get("system_package").cloned().unwrap_or(Value::Null),
"ttl_sec": ttl_sec,
"expires_at_unix_ms": expires_at_unix_ms
}))
})();
drop(state);
drop(retired_entries);
drop(pending_vm);
drop(pending_path_context);
result
}
pub(super) fn get(
&self,
lease_id: &str,
expected_sid: Option<&str>,
expected_generation: Option<u64>,
expected_profile: Option<RuntimeLeaseProfile>,
) -> Result<Arc<Mutex<RuntimeSession>>, RuntimeSessionError> {
let mut state = self.lock_state()?;
let retired_entries = Self::prune_inactive_locked(&mut state);
let result = (|| {
if let Some(entry) = state.leases.get(lease_id) {
let session = Arc::clone(&entry.session);
let session_guard = Self::try_lock_session(&session, lease_id)?;
Self::validate_session_identity(&session_guard, expected_sid, expected_generation)?;
Self::validate_session_profile(&session_guard, expected_profile)?;
drop(session_guard);
return Ok(session);
}
if let Some(tombstone) = state.tombstones.get(lease_id) {
Self::validate_tombstone_identity(tombstone, expected_sid, expected_generation)?;
Self::validate_tombstone_profile(tombstone, expected_profile)?;
return Err(tombstone.as_error());
}
Err(RuntimeSessionError {
code: "lease_not_found",
message: format!("runtime session lease `{lease_id}` was not found"),
})
})();
drop(state);
drop(retired_entries);
result
}
fn status(
&self,
lease_id: &str,
expected_sid: Option<&str>,
expected_generation: Option<u64>,
expected_profile: Option<RuntimeLeaseProfile>,
) -> Result<Value, RuntimeSessionError> {
let session = self.get(
lease_id,
expected_sid,
expected_generation,
expected_profile,
)?;
let session = Self::try_lock_session(&session, lease_id)?;
if let Some(error) = session.inactive_error() {
return Err(error);
}
Ok(session.status_payload())
}
fn list(
&self,
sid: Option<&str>,
expected_profile: Option<RuntimeLeaseProfile>,
) -> Result<Value, RuntimeSessionError> {
let mut state = self.lock_state()?;
let retired_entries = Self::prune_inactive_locked(&mut state);
let result = (|| {
let mut entries = Vec::new();
for entry in state.leases.values() {
if sid.is_some_and(|expected_sid| entry.sid != expected_sid) {
continue;
}
if expected_profile
.is_some_and(|expected_profile| entry.profile != expected_profile)
{
continue;
}
entries.push(entry);
}
entries.sort_by(|left, right| compare_runtime_session_entries(left, right));
let mut leases = Vec::with_capacity(entries.len());
for entry in entries {
leases.push(entry.list_payload()?);
}
Ok(json!({
"ok": true,
"leases": leases,
}))
})();
drop(state);
drop(retired_entries);
result
}
fn close(
&self,
lease_id: &str,
expected_sid: Option<&str>,
expected_generation: Option<u64>,
expected_profile: Option<RuntimeLeaseProfile>,
) -> Result<Value, RuntimeSessionError> {
let mut state = self.lock_state()?;
let mut retired_entries = Self::prune_inactive_locked(&mut state);
let result = (|| {
let Some((session, terminal_state)) = state.leases.get(lease_id).map(|entry| {
(
Arc::clone(&entry.session),
Arc::clone(&entry.terminal_state),
)
}) else {
if let Some(tombstone) = state.tombstones.get(lease_id) {
Self::validate_tombstone_identity(
tombstone,
expected_sid,
expected_generation,
)?;
Self::validate_tombstone_profile(tombstone, expected_profile)?;
return Err(tombstone.as_error());
}
return Err(RuntimeSessionError {
code: "lease_not_found",
message: format!("runtime session lease `{lease_id}` was not found"),
});
};
let mut session = Self::try_lock_session(&session, lease_id)?;
Self::validate_session_identity(&session, expected_sid, expected_generation)?;
Self::validate_session_profile(&session, expected_profile)?;
terminal_state.store(RuntimeSessionTerminalState::Closed as u8, Ordering::Release);
session.closed = true;
let payload = session.close_payload();
drop(session);
let retired_entry =
Self::retire_active_lease_locked(&mut state, lease_id, "lease_closed")
.expect("validated close lease must remain present during locked commit");
retired_entries.push(retired_entry);
Ok(payload)
})();
drop(state);
drop(retired_entries);
result
}
fn update_active_snapshot(
&self,
lease_id: &str,
snapshot: Value,
) -> Result<(), RuntimeSessionError> {
let mut state = self.lock_state()?;
if let Some(entry) = state.leases.get_mut(lease_id) {
entry.snapshot = snapshot;
}
Ok(())
}
#[cfg(test)]
pub(super) fn replace_active_snapshot_for_test(&self, lease_id: &str, snapshot: Value) {
let mut state = self
.lock_state()
.expect("runtime session manager state should be available in tests");
if let Some(entry) = state.leases.get_mut(lease_id) {
entry.snapshot = snapshot;
}
}
pub(super) fn take_retired_owner_tokens(&self) -> Result<Vec<u64>, RuntimeSessionError> {
let mut state = self.lock_state()?;
Ok(std::mem::take(&mut state.retired_owner_tokens))
}
fn lock_state(
&self,
) -> Result<std::sync::MutexGuard<'_, RuntimeSessionManagerState>, RuntimeSessionError> {
Ok(self
.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner))
}
fn try_lock_session<'a>(
session: &'a Arc<Mutex<RuntimeSession>>,
lease_id: &str,
) -> Result<std::sync::MutexGuard<'a, RuntimeSession>, RuntimeSessionError> {
match session.try_lock() {
Ok(session) => Ok(session),
Err(TryLockError::WouldBlock) => Err(RuntimeSessionError {
code: "lease_busy",
message: format!("runtime session lease `{lease_id}` is busy"),
}),
Err(TryLockError::Poisoned(poisoned)) => Ok(poisoned.into_inner()),
}
}
fn prune_inactive_locked(state: &mut RuntimeSessionManagerState) -> Vec<RuntimeSessionEntry> {
let now = Instant::now();
let mut removed = Vec::new();
for (lease_id, entry) in &state.leases {
let should_remove = entry
.session
.try_lock()
.map(|session| session.expires_at.is_some() && session.is_expired())
.unwrap_or_else(|error| match error {
TryLockError::WouldBlock => false,
TryLockError::Poisoned(poisoned) => {
let session = poisoned.into_inner();
session.expires_at.is_some() && session.is_expired()
}
});
if should_remove {
removed.push(lease_id.clone());
}
}
let mut retired_entries = Vec::with_capacity(removed.len());
for lease_id in removed {
if let Some(entry) = Self::retire_active_lease_locked(state, &lease_id, "lease_expired")
{
retired_entries.push(entry);
}
}
let tombstone_ttl = runtime_session_tombstone_ttl();
state.tombstones.retain(|_, tombstone| {
now.checked_duration_since(tombstone.retired_at)
.map(|age| age < tombstone_ttl)
.unwrap_or(true)
});
retired_entries
}
fn retire_active_lease_locked(
state: &mut RuntimeSessionManagerState,
lease_id: &str,
code: &'static str,
) -> Option<RuntimeSessionEntry> {
if let Some(entry) = state.leases.get(lease_id) {
entry.terminal_state.store(
runtime_session_terminal_state_from_code(code) as u8,
Ordering::Release,
);
}
let entry = state.leases.remove(lease_id)?;
if let Some(owner_token) = entry.owner_token {
state.retired_owner_tokens.push(owner_token);
}
let tombstone = RuntimeSessionTombstone::from_entry(&entry, code);
if state
.sid_index
.get(&tombstone.sid)
.is_some_and(|current| current == lease_id)
{
state.sid_index.remove(&tombstone.sid);
}
state.tombstones.insert(lease_id.to_string(), tombstone);
Some(entry)
}
fn validate_session_identity(
session: &RuntimeSession,
expected_sid: Option<&str>,
expected_generation: Option<u64>,
) -> Result<(), RuntimeSessionError> {
Self::validate_identity_parts(
&session.lease_id,
&session.sid,
session.generation,
expected_sid,
expected_generation,
)
}
fn validate_tombstone_identity(
tombstone: &RuntimeSessionTombstone,
expected_sid: Option<&str>,
expected_generation: Option<u64>,
) -> Result<(), RuntimeSessionError> {
Self::validate_identity_parts(
&tombstone.lease_id,
&tombstone.sid,
tombstone.generation,
expected_sid,
expected_generation,
)
}
fn validate_session_profile(
session: &RuntimeSession,
expected_profile: Option<RuntimeLeaseProfile>,
) -> Result<(), RuntimeSessionError> {
if let Some(expected_profile) = expected_profile
&& session.profile != expected_profile
{
return Err(RuntimeSessionError {
code: "lease_profile_mismatch",
message: format!(
"runtime session lease `{}` belongs to profile `{}`, not `{}`",
session.lease_id,
session.profile.as_str(),
expected_profile.as_str()
),
});
}
Ok(())
}
fn validate_tombstone_profile(
tombstone: &RuntimeSessionTombstone,
expected_profile: Option<RuntimeLeaseProfile>,
) -> Result<(), RuntimeSessionError> {
if let Some(expected_profile) = expected_profile
&& tombstone.profile != expected_profile
{
return Err(RuntimeSessionError {
code: "lease_profile_mismatch",
message: format!(
"runtime session lease `{}` belongs to profile `{}`, not `{}`",
tombstone.lease_id,
tombstone.profile.as_str(),
expected_profile.as_str()
),
});
}
Ok(())
}
fn validate_identity_parts(
lease_id: &str,
actual_sid: &str,
actual_generation: u64,
expected_sid: Option<&str>,
expected_generation: Option<u64>,
) -> Result<(), RuntimeSessionError> {
if let Some(expected_sid) = expected_sid
&& actual_sid != expected_sid
{
return Err(RuntimeSessionError {
code: "lease_sid_mismatch",
message: format!(
"runtime session lease `{lease_id}` belongs to sid `{actual_sid}`, not `{expected_sid}`"
),
});
}
if let Some(expected_generation) = expected_generation
&& actual_generation != expected_generation
{
return Err(RuntimeSessionError {
code: "lease_generation_mismatch",
message: format!(
"runtime session lease `{lease_id}` generation mismatch: expected {expected_generation}, actual {actual_generation}"
),
});
}
Ok(())
}
}
impl RuntimeSession {
fn inactive_error(&self) -> Option<RuntimeSessionError> {
if let Some(code) =
runtime_session_terminal_code_from_state(self.terminal_state.load(Ordering::Acquire))
{
return Some(RuntimeSessionError {
code,
message: format!(
"{} (sid `{}`, generation {})",
runtime_session_terminal_message(code, &self.lease_id),
self.sid,
self.generation
),
});
}
if self.closed {
return Some(RuntimeSessionError {
code: "lease_closed",
message: format!("runtime session lease `{}` is closed", self.lease_id),
});
}
if self.is_expired() {
return Some(RuntimeSessionError {
code: "lease_expired",
message: format!("runtime session lease `{}` is expired", self.lease_id),
});
}
None
}
fn refresh(&mut self) {
let (expires_at, expires_at_unix_ms) = runtime_session_expiry(self.ttl_sec);
self.expires_at = expires_at;
self.expires_at_unix_ms = expires_at_unix_ms;
}
fn is_expired(&self) -> bool {
self.expires_at
.is_some_and(|expires_at| Instant::now() >= expires_at)
}
fn status_payload(&self) -> Value {
json!({
"ok": runtime_session_terminal_code_from_state(
self.terminal_state.load(Ordering::Acquire),
).is_none() && !self.closed && !self.is_expired(),
"sid": self.sid.clone(),
"lease_id": self.lease_id.clone(),
"generation": self.generation,
"profile": self.profile.as_str(),
"lifetime": if self.ttl_sec.is_some() { "finite" } else { "infinite" },
"cwd": session_status_cwd_text(self),
"workspace_root": session_status_workspace_root_text(self),
"system_lua_lib": session_status_system_lua_lib_text(self),
"system_package": session_status_system_package(self),
"ttl_sec": self.ttl_sec,
"expires_at_unix_ms": self.expires_at_unix_ms,
"closed": self.closed,
"expired": self.is_expired()
})
}
fn close_payload(&self) -> Value {
json!({
"ok": true,
"sid": self.sid.clone(),
"lease_id": self.lease_id.clone(),
"generation": self.generation,
"profile": self.profile.as_str(),
"lifetime": if self.ttl_sec.is_some() { "finite" } else { "infinite" },
"cwd": session_status_cwd_text(self),
"workspace_root": session_status_workspace_root_text(self),
"system_lua_lib": session_status_system_lua_lib_text(self),
"system_package": session_status_system_package(self),
"ttl_sec": self.ttl_sec,
"expires_at_unix_ms": self.expires_at_unix_ms,
"closed": self.closed,
"expired": self.is_expired()
})
}
}
impl RuntimeSessionTombstone {
fn from_entry(entry: &RuntimeSessionEntry, code: &'static str) -> Self {
Self {
sid: entry.sid.clone(),
lease_id: entry.lease_id.clone(),
generation: entry.generation,
profile: entry.profile,
code,
retired_at: Instant::now(),
}
}
fn as_error(&self) -> RuntimeSessionError {
RuntimeSessionError {
code: self.code,
message: format!(
"{} (sid `{}`, generation {})",
runtime_session_terminal_message(self.code, &self.lease_id),
self.sid,
self.generation
),
}
}
}
fn unix_time_millis() -> u128 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_millis())
.unwrap_or(0)
}
fn session_status_cwd_text(session: &RuntimeSession) -> Option<String> {
session
.path_context
.cwd
.as_deref()
.map(render_host_visible_path)
}
fn session_status_workspace_root_text(session: &RuntimeSession) -> Option<String> {
session
.path_context
.workspace_root
.as_deref()
.map(render_host_visible_path)
}
fn session_status_system_lua_lib_text(session: &RuntimeSession) -> Option<String> {
session
.path_context
.system_lua_lib_dir
.as_deref()
.map(render_host_visible_path)
}
fn session_status_system_package(session: &RuntimeSession) -> Value {
let Some(package) = session.path_context.managed_package.as_ref() else {
return Value::Null;
};
json!({
"id": package.identity().package_id(),
"root": render_host_visible_path(package.package_root()),
"dependencies_file": package
.dependency_manifest_path()
.map(render_host_visible_path),
})
}
#[derive(Clone)]
pub(super) struct ReadonlyJsonContextValue {
value: Value,
label: String,
}
impl ReadonlyJsonContextValue {
pub(super) fn json_value(&self) -> &Value {
&self.value
}
}
#[derive(Clone)]
enum ReadonlyJsonIterationKey {
ArrayIndex(usize),
ObjectKey(String),
}
impl UserData for ReadonlyJsonContextValue {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Index, |lua, context, key: LuaValue| {
let child = match (&context.value, key) {
(Value::Object(object), LuaValue::String(key)) => {
key.to_str().ok().and_then(|key| object.get(key.as_ref()))
}
(Value::Array(array), key) => {
readonly_json_array_index(&key).and_then(|index| array.get(index))
}
_ => None,
};
match child {
Some(child) => readonly_json_context_child_to_lua(lua, child, &context.label),
None => Ok(LuaValue::Nil),
}
});
methods.add_meta_method(
MetaMethod::NewIndex,
|_, context, (_key, _value): (LuaValue, LuaValue)| -> mlua::Result<()> {
Err(mlua::Error::runtime(format!(
"{} is read-only",
context.label
)))
},
);
methods.add_meta_method(MetaMethod::Len, |_, context, ()| {
Ok(match &context.value {
Value::Array(array) => array.len(),
_ => 0,
})
});
methods.add_meta_method(MetaMethod::Pairs, |lua, context, ()| {
create_readonly_json_context_iterator(lua, context, false)
});
methods.add_meta_method(MetaMethod::IPairs, |lua, context, ()| {
create_readonly_json_context_iterator(lua, context, true)
});
}
}
fn readonly_json_array_index(key: &LuaValue) -> Option<usize> {
match key {
LuaValue::Integer(index) => index
.checked_sub(1)
.and_then(|index| usize::try_from(index).ok()),
LuaValue::Number(index)
if index.is_finite()
&& *index >= 1.0
&& index.fract() == 0.0
&& *index <= usize::MAX as f64 =>
{
Some(*index as usize - 1)
}
_ => None,
}
}
fn readonly_json_context_child_to_lua(
lua: &Lua,
value: &Value,
label: &str,
) -> mlua::Result<LuaValue> {
match value {
Value::Array(_) | Value::Object(_) => lua
.create_userdata(ReadonlyJsonContextValue {
value: value.clone(),
label: label.to_string(),
})
.map(LuaValue::UserData),
_ => json_value_to_lua(lua, value),
}
}
fn create_readonly_json_context_iterator(
lua: &Lua,
context: &ReadonlyJsonContextValue,
array_only: bool,
) -> mlua::Result<(Function, LuaValue, LuaValue)> {
let entries = match &context.value {
Value::Array(array) => array
.iter()
.enumerate()
.map(|(index, value)| {
(
ReadonlyJsonIterationKey::ArrayIndex(index + 1),
value.clone(),
)
})
.collect::<Vec<_>>(),
Value::Object(object) if !array_only => object
.iter()
.map(|(key, value)| {
(
ReadonlyJsonIterationKey::ObjectKey(key.clone()),
value.clone(),
)
})
.collect::<Vec<_>>(),
_ => Vec::new(),
};
let label = context.label.clone();
let mut entries = entries.into_iter();
let iterator = lua.create_function_mut(move |lua, _arguments: MultiValue| {
let Some((key, value)) = entries.next() else {
return Ok(MultiValue::new());
};
let key = match key {
ReadonlyJsonIterationKey::ArrayIndex(index) => LuaValue::Integer(
i64::try_from(index)
.map_err(|_| mlua::Error::runtime("read-only JSON array index overflow"))?,
),
ReadonlyJsonIterationKey::ObjectKey(key) => LuaValue::String(lua.create_string(&key)?),
};
let value = readonly_json_context_child_to_lua(lua, &value, &label)?;
Ok(MultiValue::from_vec(vec![key, value]))
})?;
Ok((iterator, LuaValue::Nil, LuaValue::Nil))
}
fn readonly_json_value_to_lua(lua: &Lua, value: &Value, label: &str) -> Result<LuaValue, String> {
readonly_json_context_child_to_lua(lua, value, label)
.map_err(|error| format!("failed to build read-only {label} Lua context: {error}"))
}
#[derive(Clone, Default)]
struct SystemPluginRuntimeContextSlot {
package: Option<Arc<ManagedRuntimePackageContext>>,
}
fn is_protected_system_runtime_context_key(key: &str) -> bool {
matches!(key, "system_plugin" | "workspace_root" | "mounts")
}
fn system_runtime_context_value(lua: &Lua, key: &str) -> mlua::Result<LuaValue> {
let package = lua
.app_data_ref::<SystemPluginRuntimeContextSlot>()
.and_then(|slot| slot.package.clone());
let Some(package) = package else {
return Ok(LuaValue::Nil);
};
let binding = package.lease_binding().ok_or_else(|| {
mlua::Error::runtime("system runtime package is missing its lease binding")
})?;
let identity = binding.identity().ok_or_else(|| {
mlua::Error::runtime("system runtime package lease identity is not bound")
})?;
match key {
"system_plugin" => readonly_json_value_to_lua(
lua,
&json!({
"id": package.identity().package_id(),
"root": render_host_visible_path(package.package_root()),
"lease_id": identity.lease_id(),
"sid": binding.sid(),
"generation": identity.generation(),
}),
"vulcan.runtime.system_plugin",
)
.map_err(mlua::Error::runtime),
"workspace_root" => match binding.workspace_root() {
Some(workspace_root) => Ok(LuaValue::String(
lua.create_string(render_host_visible_path(workspace_root))?,
)),
None => Ok(LuaValue::Nil),
},
"mounts" => readonly_json_value_to_lua(lua, binding.mounts(), "vulcan.runtime.mounts")
.map_err(mlua::Error::runtime),
_ => Ok(LuaValue::Nil),
}
}
pub(super) fn install_system_runtime_context_boundary(lua: &Lua) -> Result<(), String> {
let runtime = get_vulcan_runtime_table(lua)?;
for key in ["system_plugin", "workspace_root", "mounts"] {
runtime
.raw_set(key, LuaValue::Nil)
.map_err(|error| format!("failed to clear protected vulcan.runtime.{key}: {error}"))?;
}
let metatable = lua
.create_table()
.map_err(|error| format!("failed to create protected runtime metatable: {error}"))?;
let index = lua
.create_function(|lua, (_table, key): (Table, LuaValue)| {
let LuaValue::String(key) = key else {
return Ok(LuaValue::Nil);
};
let key = key.to_str()?;
if is_protected_system_runtime_context_key(key.as_ref()) {
system_runtime_context_value(lua, key.as_ref())
} else {
Ok(LuaValue::Nil)
}
})
.map_err(|error| format!("failed to create protected runtime index: {error}"))?;
let new_index = lua
.create_function(|_lua, (table, key, value): (Table, LuaValue, LuaValue)| {
if let LuaValue::String(key_text) = &key {
let key_text = key_text.to_str()?;
if is_protected_system_runtime_context_key(key_text.as_ref()) {
return Err(mlua::Error::runtime(format!(
"vulcan.runtime.{} is read-only",
key_text.as_ref()
)));
}
}
table.raw_set(key, value)
})
.map_err(|error| format!("failed to create protected runtime newindex: {error}"))?;
metatable
.set("__index", index)
.and_then(|()| metatable.set("__newindex", new_index))
.and_then(|()| metatable.set("__metatable", "protected System runtime context"))
.map_err(|error| format!("failed to configure protected runtime metatable: {error}"))?;
runtime
.set_metatable(Some(metatable))
.map_err(|error| format!("failed to protect vulcan.runtime context: {error}"))?;
let globals = lua.globals();
globals
.set("rawset", LuaValue::Nil)
.and_then(|()| globals.set("debug", LuaValue::Nil))
.map_err(|error| format!("failed to remove unsafe System Lua globals: {error}"))?;
let package: Table = globals
.get("package")
.map_err(|error| format!("failed to read System Lua package table: {error}"))?;
let loaded: Table = package
.get("loaded")
.map_err(|error| format!("failed to read System Lua package.loaded: {error}"))?;
loaded
.set("debug", LuaValue::Nil)
.map_err(|error| format!("failed to remove package.loaded.debug: {error}"))?;
let preload: Table = package
.get("preload")
.map_err(|error| format!("failed to read System Lua package.preload: {error}"))?;
preload
.set("debug", LuaValue::Nil)
.map_err(|error| format!("failed to remove package.preload.debug: {error}"))?;
let _previous = lua.set_app_data(SystemPluginRuntimeContextSlot::default());
Ok(())
}
fn populate_system_plugin_runtime_context(
lua: &Lua,
package: Option<&Arc<ManagedRuntimePackageContext>>,
) -> Result<(), String> {
let _previous = lua.set_app_data(SystemPluginRuntimeContextSlot {
package: package.cloned(),
});
Ok(())
}
fn runtime_session_expiry(ttl_sec: Option<u64>) -> (Option<Instant>, Option<u128>) {
let Some(ttl_sec) = ttl_sec else {
return (None, None);
};
let ttl = Duration::from_secs(ttl_sec);
(
Some(Instant::now() + ttl),
Some(unix_time_millis().saturating_add(ttl.as_millis())),
)
}
fn runtime_session_tombstone_ttl() -> Duration {
Duration::from_secs(3_600)
}
fn runtime_session_terminal_state_from_code(code: &'static str) -> RuntimeSessionTerminalState {
match code {
"lease_closed" => RuntimeSessionTerminalState::Closed,
"lease_expired" => RuntimeSessionTerminalState::Expired,
"lease_replaced" => RuntimeSessionTerminalState::Replaced,
_ => RuntimeSessionTerminalState::Active,
}
}
fn runtime_session_terminal_code_from_state(state: u8) -> Option<&'static str> {
match state {
value if value == RuntimeSessionTerminalState::Closed as u8 => Some("lease_closed"),
value if value == RuntimeSessionTerminalState::Expired as u8 => Some("lease_expired"),
value if value == RuntimeSessionTerminalState::Replaced as u8 => Some("lease_replaced"),
_ => None,
}
}
fn compare_runtime_session_entries(
left: &RuntimeSessionEntry,
right: &RuntimeSessionEntry,
) -> std::cmp::Ordering {
left.sid
.cmp(&right.sid)
.then_with(|| left.generation.cmp(&right.generation))
.then_with(|| left.lease_id.cmp(&right.lease_id))
}
fn runtime_session_terminal_message(code: &'static str, lease_id: &str) -> String {
match code {
"lease_closed" => format!("runtime session lease `{lease_id}` is closed"),
"lease_expired" => format!("runtime session lease `{lease_id}` is expired"),
"lease_replaced" => format!("runtime session lease `{lease_id}` was replaced"),
_ => format!("runtime session lease `{lease_id}` is not active"),
}
}
fn runtime_session_error_payload(error: RuntimeSessionError) -> Value {
json!({
"ok": false,
"error_code": error.code,
"message": error.message
})
}
fn normalize_runtime_session_sid(value: &str) -> Result<String, String> {
let sid = value.trim();
if sid.is_empty() {
return Err("runtime session sid must not be empty".to_string());
}
if sid.len() > 128 {
return Err("runtime session sid must not exceed 128 bytes".to_string());
}
if sid.contains('\0') {
return Err("runtime session sid must not contain NUL bytes".to_string());
}
Ok(sid.to_string())
}
fn normalize_optional_runtime_lease_path(
value: Option<&str>,
field_name: &str,
) -> Result<Option<PathBuf>, String> {
let Some(value) = value else {
return Ok(None);
};
let trimmed = value.trim();
if trimmed.is_empty() {
return Ok(None);
}
if trimmed.contains('\0') {
return Err(format!("{field_name} must not contain NUL bytes"));
}
#[cfg(windows)]
if has_invalid_windows_path_syntax(trimmed) {
return Err(format!(
"{field_name} contains unsupported Windows path syntax"
));
}
Ok(Some(PathBuf::from(trimmed)))
}
fn canonicalize_system_runtime_directory(path: &Path, field_name: &str) -> Result<PathBuf, String> {
if !path.is_absolute() {
return Err(format!("{field_name} must be an absolute path"));
}
let canonical = fs::canonicalize(path).map_err(|error| {
format!(
"failed to canonicalize {field_name} {}: {error}",
render_host_visible_path(path)
)
})?;
let metadata = fs::metadata(&canonical).map_err(|error| {
format!(
"failed to inspect {field_name} {}: {error}",
render_host_visible_path(&canonical)
)
})?;
if !metadata.is_dir() {
return Err(format!(
"{field_name} is not a directory: {}",
render_host_visible_path(&canonical)
));
}
Ok(canonical)
}
fn resolve_system_runtime_cwd(
requested: Option<&str>,
package_root: &Path,
workspace_root: Option<&Path>,
) -> Result<PathBuf, String> {
let candidate = match normalize_optional_runtime_lease_path(requested, "cwd")? {
Some(path) if path.is_absolute() => path,
Some(path) => package_root.join(path),
None => package_root.to_path_buf(),
};
let canonical = fs::canonicalize(&candidate).map_err(|error| {
format!(
"failed to canonicalize cwd {}: {error}",
render_host_visible_path(&candidate)
)
})?;
if !fs::metadata(&canonical)
.map_err(|error| {
format!(
"failed to inspect cwd {}: {error}",
render_host_visible_path(&canonical)
)
})?
.is_dir()
{
return Err(format!(
"cwd is not a directory: {}",
render_host_visible_path(&canonical)
));
}
if !canonical.starts_with(package_root)
&& !workspace_root.is_some_and(|root| canonical.starts_with(root))
{
return Err(format!(
"cwd {} is outside the System package root and authorized workspace root",
render_host_visible_path(&canonical)
));
}
Ok(canonical)
}
fn normalize_runtime_lease_path_list(
values: &[String],
field_name: &str,
) -> Result<Vec<PathBuf>, String> {
let mut normalized = Vec::new();
for (index, value) in values.iter().enumerate() {
let item = normalize_optional_runtime_lease_path(
Some(value.as_str()),
&format!("{field_name}[{index}]"),
)?;
if let Some(item) = item {
normalized.push(item);
}
}
Ok(normalized)
}
impl LuaEngine {
fn runtime_session_manager(&self, profile: RuntimeLeaseProfile) -> &Arc<RuntimeSessionManager> {
match profile {
RuntimeLeaseProfile::Public => &self.public_runtime_sessions,
RuntimeLeaseProfile::SystemLuaLib => &self.system_runtime_sessions,
}
}
fn resolve_system_lua_lib_dir(&self) -> Result<PathBuf, String> {
let configured = self
.host_options
.system_lua_lib_dir
.as_ref()
.ok_or_else(|| "system runtime leases require system_lua_lib_dir".to_string())?;
let canonical = std::fs::canonicalize(configured).map_err(|error| {
format!(
"failed to canonicalize system_lua_lib_dir {}: {}",
render_host_visible_path(configured),
error
)
})?;
if !canonical.is_dir() {
return Err(format!(
"system_lua_lib_dir is not a directory: {}",
render_host_visible_path(&canonical)
));
}
Ok(canonical)
}
fn resolve_runtime_lease_ttl(
profile: RuntimeLeaseProfile,
requested_ttl_sec: Option<u64>,
) -> Result<Option<u64>, String> {
match profile {
RuntimeLeaseProfile::Public => match requested_ttl_sec {
Some(0) => Err("runtime lease ttl_sec must be greater than 0".to_string()),
Some(value) => Ok(Some(value.clamp(1, 3_600))),
None => Ok(Some(default_runtime_session_ttl_sec())),
},
RuntimeLeaseProfile::SystemLuaLib => match requested_ttl_sec {
None | Some(0) => Ok(None),
Some(value) => Ok(Some(value.clamp(1, 3_600))),
},
}
}
fn resolve_public_runtime_lease_path_context(
&self,
request: &RuntimeSessionCreateRequest,
) -> Result<RuntimeLeasePathContext, String> {
let context = RuntimeLeasePathContext {
cwd: normalize_optional_runtime_lease_path(request.cwd.as_deref(), "cwd")?,
cwd_identity: None,
workspace_root: normalize_optional_runtime_lease_path(
request.workspace_root.as_deref(),
"workspace_root",
)?,
workspace_root_identity: None,
lua_roots: normalize_runtime_lease_path_list(&request.lua_roots, "lua_roots")?,
c_roots: normalize_runtime_lease_path_list(&request.c_roots, "c_roots")?,
mounts: request.mounts.clone(),
system_lua_lib_dir: None,
managed_package: None,
};
if !context.mounts.is_object() && !context.mounts.is_null() {
return Err("runtime lease mounts must be one JSON object when present".to_string());
}
Ok(context)
}
fn resolve_system_runtime_lease_path_context(
&self,
request: &SystemRuntimeSessionCreateRequest,
) -> Result<RuntimeLeasePathContext, String> {
if !request.mounts.is_object() {
return Err("system runtime lease mounts must be one JSON object".to_string());
}
let system_lua_lib_dir = self.resolve_system_lua_lib_dir()?;
let runtime_root = self
.host_options
.runtime_root
.as_ref()
.ok_or_else(|| "system runtime leases require runtime_root".to_string())?;
let workspace_root = normalize_optional_runtime_lease_path(
request.workspace_root.as_deref(),
"workspace_root",
)?
.map(|path| canonicalize_system_runtime_directory(&path, "workspace_root"))
.transpose()?;
let workspace_root_identity = workspace_root
.as_deref()
.map(|path| capture_managed_directory_identity(path, "workspace_root"))
.transpose()?;
let lease_binding = Arc::new(ManagedRuntimeLeaseBinding::new(
request.sid.clone(),
workspace_root.clone(),
request.mounts.clone(),
));
let managed_runtime_roots = self.managed_runtime_roots_for(runtime_root)?;
let managed_package = ManagedRuntimePackageContext::for_system_plugin_with_roots(
&request.system_package.id,
Path::new(&request.system_package.root),
managed_runtime_roots,
&system_lua_lib_dir,
&request.system_package.dependencies_file,
lease_binding,
)?;
let cwd = resolve_system_runtime_cwd(
request.cwd.as_deref(),
managed_package.package_root(),
workspace_root.as_deref(),
)?;
let cwd_identity = capture_managed_directory_identity(&cwd, "cwd")?;
Ok(RuntimeLeasePathContext {
cwd: Some(cwd),
cwd_identity: Some(cwd_identity),
workspace_root,
workspace_root_identity,
lua_roots: vec![managed_package.package_root().to_path_buf()],
c_roots: Vec::new(),
mounts: request.mounts.clone(),
system_lua_lib_dir: Some(system_lua_lib_dir),
managed_package: Some(managed_package),
})
}
fn runtime_lease_package_search_path_text(
value: &mlua::String,
field_name: &str,
) -> Result<String, String> {
value
.to_str()
.map(|text| text.to_string())
.map_err(|error| {
format!("runtime lease package.{field_name} is not valid UTF-8: {error}")
})
}
fn configure_runtime_lease_vm(
lua: &Lua,
path_context: &RuntimeLeasePathContext,
) -> Result<(), String> {
let package: Table = lua.globals().get("package").map_err(|error| {
format!(
"Failed to get Lua package table for runtime lease: {}",
error
)
})?;
let old_cpath: mlua::String = package.get("cpath").map_err(|error| {
format!("Failed to read package.cpath for runtime lease: {}", error)
})?;
let old_path: mlua::String = package
.get("path")
.map_err(|error| format!("Failed to read package.path for runtime lease: {}", error))?;
let mut cpath_prefix = String::new();
for root in &path_context.c_roots {
validate_lua_search_root_path(root, "c_roots")?;
#[cfg(windows)]
{
cpath_prefix.push_str(&format!(
"{}\\?.dll;{}\\?\\init.dll;",
root.display(),
root.display()
));
}
#[cfg(target_os = "linux")]
{
cpath_prefix.push_str(&format!(
"{}/?.so;{}/?/init.so;",
root.display(),
root.display()
));
}
#[cfg(target_os = "macos")]
{
cpath_prefix.push_str(&format!(
"{}/?.dylib;{}/?/init.dylib;",
root.display(),
root.display()
));
}
}
let mut path_prefix = String::new();
for root in &path_context.lua_roots {
validate_lua_search_root_path(root, "lua_roots")?;
#[cfg(windows)]
{
path_prefix.push_str(&format!(
"{}\\?.lua;{}\\?\\init.lua;",
root.display(),
root.display()
));
}
#[cfg(unix)]
{
path_prefix.push_str(&format!(
"{}/?.lua;{}/?/init.lua;",
root.display(),
root.display()
));
}
}
if !cpath_prefix.is_empty() {
let old_cpath_text = Self::runtime_lease_package_search_path_text(&old_cpath, "cpath")?;
let new_cpath = format!("{}{}", cpath_prefix, old_cpath_text);
package
.set(
"cpath",
lua.create_string(&new_cpath).map_err(|error| {
format!("Failed to build runtime lease cpath string: {}", error)
})?,
)
.map_err(|error| format!("Failed to set runtime lease package.cpath: {}", error))?;
}
if !path_prefix.is_empty() {
let old_path_text = Self::runtime_lease_package_search_path_text(&old_path, "path")?;
let new_path = format!("{}{}", path_prefix, old_path_text);
package
.set(
"path",
lua.create_string(&new_path).map_err(|error| {
format!("Failed to build runtime lease path string: {}", error)
})?,
)
.map_err(|error| format!("Failed to set runtime lease package.path: {}", error))?;
}
Ok(())
}
fn eval_lua_value_with_optional_cwd(
lua: &Lua,
wrapper: &str,
cwd: Option<&Path>,
) -> Result<LuaValue, mlua::Error> {
match cwd {
Some(cwd) => {
let _cwd_guard = lock_runlua_cwd_guard();
let original_dir = std::env::current_dir().map_err(|error| {
mlua::Error::runtime(format!("runtime lease cwd: {}", error))
})?;
std::env::set_current_dir(cwd).map_err(|error| {
mlua::Error::runtime(format!(
"runtime lease set cwd {}: {}",
render_host_visible_path(cwd),
error
))
})?;
let execution = lua.load(wrapper).eval::<LuaValue>();
let restore_result = std::env::set_current_dir(&original_dir).map_err(|error| {
mlua::Error::runtime(format!("runtime lease restore cwd: {}", error))
});
match (execution, restore_result) {
(Ok(value), Ok(())) => Ok(value),
(Err(error), Ok(())) => Err(error),
(_, Err(error)) => Err(error),
}
}
None => lua.load(wrapper).eval::<LuaValue>(),
}
}
pub fn create_runtime_lease_json(&self, request_json: &str) -> Result<String, String> {
self.create_public_runtime_session_json(request_json)
}
fn create_public_runtime_session_json(&self, request_json: &str) -> Result<String, String> {
let mut request: RuntimeSessionCreateRequest = serde_json::from_str(request_json)
.map_err(|error| format!("Invalid runtime session create JSON: {}", error))?;
request.sid = normalize_runtime_session_sid(&request.sid)?;
let ttl_sec =
Self::resolve_runtime_lease_ttl(RuntimeLeaseProfile::Public, request.ttl_sec)?;
let path_context = self.resolve_public_runtime_lease_path_context(&request)?;
let vm = Self::create_runlua_vm(RunLuaVmBuildContext::from_engine(
self,
&self.skills,
&self.entry_registry,
))?;
Self::configure_runtime_lease_vm(&vm.lua, &path_context)?;
Self::install_managed_io_compat_for_runtime(&vm.lua, self.host_options.as_ref())?;
let manager = self.runtime_session_manager(RuntimeLeaseProfile::Public);
let result = manager.insert(
RuntimeLeaseProfile::Public,
request.sid,
ttl_sec,
request.replace,
path_context,
vm,
);
self.retire_runtime_session_manager_owners(manager.as_ref(), "public lease create");
let payload = result.unwrap_or_else(runtime_session_error_payload);
serde_json::to_string(&payload)
.map_err(|error| format!("Runtime session create JSON encode failed: {}", error))
}
pub fn eval_runtime_lease_json(&self, request_json: &str) -> Result<String, String> {
self.eval_runtime_session_with_profile_json(request_json, RuntimeLeaseProfile::Public)
}
fn eval_runtime_session_with_profile_json(
&self,
request_json: &str,
expected_profile: RuntimeLeaseProfile,
) -> Result<String, String> {
let mut request: RuntimeSessionEvalRequest = serde_json::from_str(request_json)
.map_err(|error| format!("Invalid runtime session eval JSON: {}", error))?;
if let Some(sid) = request.sid.as_mut() {
*sid = normalize_runtime_session_sid(sid)?;
}
if request.timeout_ms == 0 {
return Err("runtime session eval timeout_ms must be greater than 0".to_string());
}
let manager = self.runtime_session_manager(expected_profile);
let session_result = manager.get(
&request.lease_id,
request.sid.as_deref(),
request.generation,
Some(expected_profile),
);
self.retire_runtime_session_manager_owners(manager.as_ref(), "lease eval lookup");
let session = match session_result {
Ok(session) => session,
Err(error) => {
return serde_json::to_string(&runtime_session_error_payload(error)).map_err(
|encode_error| {
format!("Runtime session eval JSON encode failed: {}", encode_error)
},
);
}
};
let mut session = match RuntimeSessionManager::try_lock_session(&session, &request.lease_id)
{
Ok(session) => session,
Err(error) => {
let payload = runtime_session_error_payload(error);
return serde_json::to_string(&payload).map_err(|error| {
format!("Runtime session eval JSON encode failed: {}", error)
});
}
};
let (payload, refreshed_snapshot) = match Self::ensure_runtime_session_active(&mut session)
{
Ok(()) => match self.eval_runtime_session_locked(&mut session, &request) {
Ok(result) => {
session.refresh();
(
json!({
"ok": true,
"sid": session.sid.clone(),
"lease_id": session.lease_id.clone(),
"generation": session.generation,
"profile": session.profile.as_str(),
"lifetime": if session.ttl_sec.is_some() { "finite" } else { "infinite" },
"cwd": session_status_cwd_text(&session),
"workspace_root": session_status_workspace_root_text(&session),
"system_lua_lib": session_status_system_lua_lib_text(&session),
"system_package": session_status_system_package(&session),
"expires_at_unix_ms": session.expires_at_unix_ms,
"result": result
}),
Some(session.status_payload()),
)
}
Err(message) => (
runtime_session_error_payload(RuntimeSessionError {
code: "eval_failed",
message,
}),
Some(session.status_payload()),
),
},
Err(error) => (runtime_session_error_payload(error), None),
};
drop(session);
if let Some(snapshot) = refreshed_snapshot {
let _ = self
.runtime_session_manager(expected_profile)
.update_active_snapshot(&request.lease_id, snapshot);
}
serde_json::to_string(&payload)
.map_err(|error| format!("Runtime session eval JSON encode failed: {}", error))
}
pub fn runtime_lease_status_json(&self, request_json: &str) -> Result<String, String> {
self.runtime_session_status_with_profile_json(request_json, RuntimeLeaseProfile::Public)
}
fn runtime_session_status_with_profile_json(
&self,
request_json: &str,
expected_profile: RuntimeLeaseProfile,
) -> Result<String, String> {
let mut request: RuntimeSessionLeaseRequest = serde_json::from_str(request_json)
.map_err(|error| format!("Invalid runtime session status JSON: {}", error))?;
if let Some(sid) = request.sid.as_mut() {
*sid = normalize_runtime_session_sid(sid)?;
}
let manager = self.runtime_session_manager(expected_profile);
let result = manager.status(
&request.lease_id,
request.sid.as_deref(),
request.generation,
Some(expected_profile),
);
self.retire_runtime_session_manager_owners(manager.as_ref(), "lease status");
let payload = result.unwrap_or_else(runtime_session_error_payload);
serde_json::to_string(&payload)
.map_err(|error| format!("Runtime session status JSON encode failed: {}", error))
}
pub fn list_runtime_leases_json(&self, request_json: &str) -> Result<String, String> {
self.list_runtime_sessions_with_profile_json(request_json, RuntimeLeaseProfile::Public)
}
fn list_runtime_sessions_with_profile_json(
&self,
request_json: &str,
expected_profile: RuntimeLeaseProfile,
) -> Result<String, String> {
let mut request: RuntimeSessionListRequest = serde_json::from_str(request_json)
.map_err(|error| format!("Invalid runtime session list JSON: {}", error))?;
if let Some(sid) = request.sid.as_mut() {
*sid = normalize_runtime_session_sid(sid)?;
}
let manager = self.runtime_session_manager(expected_profile);
let result = manager.list(request.sid.as_deref(), Some(expected_profile));
self.retire_runtime_session_manager_owners(manager.as_ref(), "lease list");
let payload = result.unwrap_or_else(runtime_session_error_payload);
serde_json::to_string(&payload)
.map_err(|error| format!("Runtime session list JSON encode failed: {}", error))
}
pub fn close_runtime_lease_json(&self, request_json: &str) -> Result<String, String> {
self.close_runtime_session_with_profile_json(request_json, RuntimeLeaseProfile::Public)
}
fn close_runtime_session_with_profile_json(
&self,
request_json: &str,
expected_profile: RuntimeLeaseProfile,
) -> Result<String, String> {
let mut request: RuntimeSessionLeaseRequest = serde_json::from_str(request_json)
.map_err(|error| format!("Invalid runtime session close JSON: {}", error))?;
if let Some(sid) = request.sid.as_mut() {
*sid = normalize_runtime_session_sid(sid)?;
}
let manager = self.runtime_session_manager(expected_profile);
let result = manager.close(
&request.lease_id,
request.sid.as_deref(),
request.generation,
Some(expected_profile),
);
self.retire_runtime_session_manager_owners(manager.as_ref(), "lease close");
let payload = result.unwrap_or_else(runtime_session_error_payload);
serde_json::to_string(&payload)
.map_err(|error| format!("Runtime session close JSON encode failed: {}", error))
}
pub fn create_system_runtime_lease_json(&self, request_json: &str) -> Result<String, String> {
let mut request: SystemRuntimeSessionCreateRequest = serde_json::from_str(request_json)
.map_err(|error| format!("Invalid system runtime session create JSON: {}", error))?;
request.sid = normalize_runtime_session_sid(&request.sid)?;
let ttl_sec =
Self::resolve_runtime_lease_ttl(RuntimeLeaseProfile::SystemLuaLib, request.ttl_sec)?;
let path_context = self.resolve_system_runtime_lease_path_context(&request)?;
let vm = self.create_system_runtime_vm()?;
Self::configure_runtime_lease_vm(&vm.lua, &path_context)?;
Self::install_managed_io_compat_for_runtime(&vm.lua, self.host_options.as_ref())?;
let manager = self.runtime_session_manager(RuntimeLeaseProfile::SystemLuaLib);
let result = manager.insert(
RuntimeLeaseProfile::SystemLuaLib,
request.sid,
ttl_sec,
request.replace,
path_context,
vm,
);
self.retire_runtime_session_manager_owners(manager.as_ref(), "System lease create");
let payload = result.unwrap_or_else(runtime_session_error_payload);
serde_json::to_string(&payload).map_err(|error| {
format!(
"System runtime session create JSON encode failed: {}",
error
)
})
}
pub fn eval_system_runtime_lease_json(&self, request_json: &str) -> Result<String, String> {
self.eval_runtime_session_with_profile_json(request_json, RuntimeLeaseProfile::SystemLuaLib)
}
pub fn system_runtime_lease_status_json(&self, request_json: &str) -> Result<String, String> {
self.runtime_session_status_with_profile_json(
request_json,
RuntimeLeaseProfile::SystemLuaLib,
)
}
pub fn list_system_runtime_leases_json(&self, request_json: &str) -> Result<String, String> {
self.list_runtime_sessions_with_profile_json(
request_json,
RuntimeLeaseProfile::SystemLuaLib,
)
}
pub fn close_system_runtime_lease_json(&self, request_json: &str) -> Result<String, String> {
self.close_runtime_session_with_profile_json(
request_json,
RuntimeLeaseProfile::SystemLuaLib,
)
}
fn install_managed_io_compat_for_runtime(
lua: &Lua,
host_options: &LuaRuntimeHostOptions,
) -> Result<(), String> {
if !host_options.capabilities.enable_managed_io_compat {
return Ok(());
}
let default_encoding = resolve_host_default_text_encoding(host_options)?;
let vulcan = get_vulcan_table(lua)?;
let vulcan_io = vulcan
.get::<Table>("io")
.map_err(|error| format!("Failed to get vulcan.io: {}", error))?;
install_managed_io_compat(lua, &vulcan_io, default_encoding).map_err(|error| {
format!(
"Failed to install managed io compatibility for runtime session: {}",
error
)
})
}
pub(super) fn ensure_runtime_session_active(
session: &mut RuntimeSession,
) -> Result<(), RuntimeSessionError> {
if let Some(error) = session.inactive_error() {
return Err(error);
}
if session.ttl_sec.is_some() {
session.refresh();
}
Ok(())
}
fn eval_runtime_session_locked(
&self,
session: &mut RuntimeSession,
request: &RuntimeSessionEvalRequest,
) -> Result<Value, String> {
if let Some(package) = session.path_context.managed_package.as_ref() {
package.validate_live_filesystem_identity()?;
}
session
.path_context
.validate_live_system_directory_identities()?;
reset_pooled_vm_request_scope(&session.vm.lua, self.host_options.as_ref())?;
let transaction_guard = session
.path_context
.managed_package
.as_ref()
.map(|package| {
self.managed_runtime_services
.begin_transaction(package.owner_token())
})
.transpose()?;
let transaction_context = transaction_guard.as_ref().map(|guard| guard.context());
let _transaction_scope =
LuaManagedRuntimeTransactionScopeGuard::install(&session.vm.lua, transaction_context);
let invocation_context = request.to_invocation_context();
Self::populate_anonymous_lua_context(
&session.vm.lua,
AnonymousLuaExecutionContext {
invocation_context: Some(&invocation_context),
internal_context: VulcanInternalExecutionContext {
tool_name: None,
skill_name: None,
entry_name: None,
root_name: None,
luaexec_active: true,
luaexec_caller_tool_name: None,
},
entry_file: None,
dependency_context: AnonymousLuaDependencyContext::ClearWithHostOptions(
self.host_options.as_ref(),
),
managed_package_context: session.path_context.managed_package.as_ref().map_or(
AnonymousLuaManagedPackageContext::Clear,
AnonymousLuaManagedPackageContext::Set,
),
},
)?;
populate_system_plugin_runtime_context(
&session.vm.lua,
session.path_context.managed_package.as_ref(),
)?;
let args_table = json_to_lua_table(&session.vm.lua, &request.args)?;
session
.vm
.lua
.globals()
.set("__runlua_args", args_table)
.map_err(|error| format!("Failed to set runtime session args: {}", error))?;
let wrapper = format!(
"return (function()\n local args = __runlua_args\n {}\nend)()",
request.code
);
Self::install_runlua_timeout_guard(&session.vm.lua, request.timeout_ms)
.map_err(|error| error.to_string())?;
let eval_result = Self::eval_lua_value_with_optional_cwd(
&session.vm.lua,
&wrapper,
session.path_context.cwd.as_deref(),
);
Self::remove_runlua_timeout_guard(&session.vm.lua);
let result = eval_result.map_err(|error| {
let msg = format!("Runtime session eval error: {}", error);
log_error(format!("[LuaSkill:error] {}", msg));
msg
})?;
let json_result = lua_value_to_json(&result)?;
clear_runlua_args_global(&session.vm.lua)?;
if let Some(transaction_guard) = transaction_guard {
transaction_guard.commit()?;
}
Ok(json_result)
}
}
#[cfg(test)]
mod tests {
use super::super::LuaVm;
use super::{
RuntimeLeasePathContext, RuntimeLeaseProfile, RuntimeSessionManager,
RuntimeSessionTerminalState, next_runtime_session_generation,
next_runtime_session_sequence,
};
use crate::runtime::managed_package::{
ManagedRuntimeLeaseBinding, ManagedRuntimePackageContext,
};
use crate::runtime::path::render_host_visible_path;
use mlua::{Lua, UserData};
use serde_json::{Value, json};
use std::fs;
use std::panic::{self, AssertUnwindSafe};
use std::sync::{Arc, mpsc};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
struct ManagerStateDropProbe {
manager: Arc<RuntimeSessionManager>,
observation: mpsc::Sender<bool>,
}
impl UserData for ManagerStateDropProbe {}
impl Drop for ManagerStateDropProbe {
fn drop(&mut self) {
let manager_state_is_unlocked = self.manager.state.try_lock().is_ok();
let _ = self.observation.send(manager_state_is_unlocked);
}
}
fn public_test_path_context() -> RuntimeLeasePathContext {
RuntimeLeasePathContext {
cwd: None,
cwd_identity: None,
workspace_root: None,
workspace_root_identity: None,
lua_roots: Vec::new(),
c_roots: Vec::new(),
mounts: json!({}),
system_lua_lib_dir: None,
managed_package: None,
}
}
fn system_test_path_context(
system_lua_root: &std::path::Path,
package: Arc<ManagedRuntimePackageContext>,
) -> RuntimeLeasePathContext {
RuntimeLeasePathContext {
cwd: None,
cwd_identity: None,
workspace_root: None,
workspace_root_identity: None,
lua_roots: Vec::new(),
c_roots: Vec::new(),
mounts: json!({}),
system_lua_lib_dir: Some(system_lua_root.to_path_buf()),
managed_package: Some(package),
}
}
fn plain_test_lua_vm() -> LuaVm {
LuaVm {
lua: Lua::new(),
last_used_at: Instant::now(),
}
}
fn drop_probed_test_lua_vm(
manager: &Arc<RuntimeSessionManager>,
observation: mpsc::Sender<bool>,
) -> LuaVm {
let lua = Lua::new();
let probe = lua
.create_userdata(ManagerStateDropProbe {
manager: Arc::clone(manager),
observation,
})
.expect("manager state drop probe should be created");
lua.globals()
.set("__runtime_session_manager_drop_probe", probe)
.expect("manager state drop probe should be retained by Lua globals");
LuaVm {
lua,
last_used_at: Instant::now(),
}
}
fn insert_public_test_lease(
manager: &RuntimeSessionManager,
sid: &str,
replace: bool,
vm: LuaVm,
) -> Value {
manager
.insert(
RuntimeLeaseProfile::Public,
sid.to_string(),
None,
replace,
public_test_path_context(),
vm,
)
.unwrap_or_else(|error| {
panic!(
"test runtime lease insertion failed: {}: {}",
error.code, error.message
)
})
}
fn test_lease_id(payload: &Value) -> String {
payload["lease_id"]
.as_str()
.expect("test lease id should be a string")
.to_string()
}
fn assert_drop_observed_after_manager_unlock(
observation: mpsc::Receiver<bool>,
operation: &str,
) {
let manager_state_was_unlocked = observation
.recv_timeout(Duration::from_secs(2))
.unwrap_or_else(|error| panic!("{operation} VM drop was not observed: {error}"));
assert!(
manager_state_was_unlocked,
"{operation} destroyed a runtime-session VM while holding manager state"
);
}
fn assert_test_lease_remains_active(
manager: &RuntimeSessionManager,
sid: &str,
lease_id: &str,
generation: u64,
) {
let state = manager
.lock_state()
.expect("runtime session manager state should remain available");
assert_eq!(state.sid_index.get(sid).map(String::as_str), Some(lease_id));
let entry = state
.leases
.get(lease_id)
.expect("old runtime lease should remain active");
assert_eq!(entry.sid, sid);
assert_eq!(entry.generation, generation);
assert_eq!(
entry
.terminal_state
.load(std::sync::atomic::Ordering::Acquire),
RuntimeSessionTerminalState::Active as u8
);
assert!(!state.tombstones.contains_key(lease_id));
}
#[test]
fn runtime_lease_package_search_path_text_preserves_valid_utf8() {
let lua = mlua::Lua::new();
let search_path = lua
.create_string("?.lua;?/init.lua;")
.expect("valid package search path string should be created");
let decoded =
super::LuaEngine::runtime_lease_package_search_path_text(&search_path, "path")
.expect("valid package search path should decode");
assert_eq!(decoded, "?.lua;?/init.lua;");
}
#[test]
fn runtime_lease_package_search_path_text_rejects_invalid_utf8() {
let lua = mlua::Lua::new();
let search_path = lua
.create_string([0xff])
.expect("invalid package search path bytes should be created");
let error = super::LuaEngine::runtime_lease_package_search_path_text(&search_path, "cpath")
.expect_err("invalid package search path should be rejected");
assert!(
error.contains("runtime lease package.cpath is not valid UTF-8"),
"unexpected error: {}",
error
);
}
#[test]
fn runtime_session_manager_state_recovers_after_poisoned_lock() {
let manager = RuntimeSessionManager::new();
let poison_result = panic::catch_unwind(AssertUnwindSafe(|| {
let _guard = manager
.state
.lock()
.expect("initial runtime session manager lock");
panic!("poison runtime session manager state for recovery test");
}));
assert!(poison_result.is_err());
let state = manager
.lock_state()
.expect("recover runtime session manager state");
assert!(state.leases.is_empty());
assert!(state.sid_index.is_empty());
}
#[test]
fn runtime_session_sequence_exhaustion_is_explicit() {
let error = next_runtime_session_sequence(u64::MAX)
.expect_err("maximum lease sequence must be rejected");
assert_eq!(error.code, "lease_sequence_exhausted");
assert!(error.message.contains("sequence is exhausted"));
}
#[test]
fn runtime_session_generation_exhaustion_is_explicit() {
let error = next_runtime_session_generation(u64::MAX, "exhausted-sid")
.expect_err("maximum lease generation must be rejected");
assert_eq!(error.code, "lease_generation_exhausted");
assert!(error.message.contains("`exhausted-sid`"));
}
#[test]
fn runtime_session_failed_replace_is_atomic() {
let manager = Arc::new(RuntimeSessionManager::new());
let sid = "atomic-replace";
let created = insert_public_test_lease(&manager, sid, false, plain_test_lua_vm());
let lease_id = test_lease_id(&created);
let generation = created["generation"]
.as_u64()
.expect("initial test lease generation");
{
let mut state = manager
.lock_state()
.expect("set exhausted runtime lease sequence");
state.next_sequence = u64::MAX;
}
let (sequence_drop_tx, sequence_drop_rx) = mpsc::channel();
let sequence_error = manager
.insert(
RuntimeLeaseProfile::Public,
sid.to_string(),
None,
true,
public_test_path_context(),
drop_probed_test_lua_vm(&manager, sequence_drop_tx),
)
.expect_err("sequence exhaustion should reject replacement");
assert_eq!(sequence_error.code, "lease_sequence_exhausted");
assert_drop_observed_after_manager_unlock(sequence_drop_rx, "sequence rejection");
assert_test_lease_remains_active(&manager, sid, &lease_id, generation);
{
let mut state = manager
.lock_state()
.expect("restore committed runtime lease sequence");
state.next_sequence = 1;
}
let (binding_drop_tx, binding_drop_rx) = mpsc::channel();
let binding_error = manager
.insert(
RuntimeLeaseProfile::SystemLuaLib,
sid.to_string(),
None,
true,
public_test_path_context(),
drop_probed_test_lua_vm(&manager, binding_drop_tx),
)
.expect_err("missing System package should reject replacement");
assert_eq!(binding_error.code, "system_package_missing");
assert_drop_observed_after_manager_unlock(binding_drop_rx, "System binding rejection");
assert_test_lease_remains_active(&manager, sid, &lease_id, generation);
{
let state = manager
.lock_state()
.expect("inspect counters after failed System binding validation");
assert_eq!(state.next_sequence, 1);
assert_eq!(state.generations.get(sid).copied(), Some(generation));
}
{
let mut state = manager
.lock_state()
.expect("set exhausted runtime lease generation");
state.generations.insert(sid.to_string(), u64::MAX);
}
let (generation_drop_tx, generation_drop_rx) = mpsc::channel();
let generation_error = manager
.insert(
RuntimeLeaseProfile::Public,
sid.to_string(),
None,
true,
public_test_path_context(),
drop_probed_test_lua_vm(&manager, generation_drop_tx),
)
.expect_err("generation exhaustion should reject replacement");
assert_eq!(generation_error.code, "lease_generation_exhausted");
assert_drop_observed_after_manager_unlock(generation_drop_rx, "generation rejection");
assert_test_lease_remains_active(&manager, sid, &lease_id, generation);
}
#[test]
fn runtime_session_system_binding_failure_preserves_old_lease() {
let unique_suffix = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system clock should follow the Unix epoch")
.as_nanos();
let runtime_root = std::env::temp_dir().join(format!(
"luaskills_atomic_system_binding_{}_{}",
std::process::id(),
unique_suffix
));
let system_lua_root = runtime_root.join("system_lua_lib");
let package_root = system_lua_root.join("atomic-system-package");
let dependencies_file = package_root.join("dependencies.yaml");
fs::create_dir_all(&package_root).expect("create atomic System package root");
fs::write(&dependencies_file, "{}\n")
.expect("write atomic System package dependency manifest");
let canonical_system_lua_root =
fs::canonicalize(&system_lua_root).expect("canonicalize atomic System trust root");
let sid = "atomic-system-binding";
let initial_binding = Arc::new(ManagedRuntimeLeaseBinding::new(
sid.to_string(),
None,
json!({}),
));
let initial_package = ManagedRuntimePackageContext::for_system_plugin(
"atomic-system-package",
&package_root,
&runtime_root,
&canonical_system_lua_root,
"dependencies.yaml",
initial_binding,
)
.expect("build initial atomic System package context");
let manager = Arc::new(RuntimeSessionManager::new());
let created = manager
.insert(
RuntimeLeaseProfile::SystemLuaLib,
sid.to_string(),
None,
false,
system_test_path_context(&canonical_system_lua_root, initial_package),
plain_test_lua_vm(),
)
.expect("initial atomic System lease should be inserted");
let lease_id = test_lease_id(&created);
let generation = created["generation"]
.as_u64()
.expect("initial atomic System generation");
let replacement_binding = Arc::new(ManagedRuntimeLeaseBinding::new(
sid.to_string(),
None,
json!({}),
));
replacement_binding
.bind("already-bound-test-lease".to_string(), 99)
.expect("replacement binding should be pre-bound exactly once");
let replacement_package = ManagedRuntimePackageContext::for_system_plugin(
"atomic-system-package",
&package_root,
&runtime_root,
&canonical_system_lua_root,
"dependencies.yaml",
replacement_binding,
)
.expect("build replacement atomic System package context");
let (drop_tx, drop_rx) = mpsc::channel();
let error = manager
.insert(
RuntimeLeaseProfile::SystemLuaLib,
sid.to_string(),
None,
true,
system_test_path_context(&canonical_system_lua_root, replacement_package),
drop_probed_test_lua_vm(&manager, drop_tx),
)
.expect_err("already-bound System package should reject replacement");
assert_eq!(error.code, "system_package_binding_failed");
assert_drop_observed_after_manager_unlock(drop_rx, "System binding failure");
assert_test_lease_remains_active(&manager, sid, &lease_id, generation);
{
let state = manager
.lock_state()
.expect("inspect counters after failed System bind");
assert_eq!(state.next_sequence, 1);
assert_eq!(state.generations.get(sid).copied(), Some(generation));
}
manager
.close(
&lease_id,
Some(sid),
Some(generation),
Some(RuntimeLeaseProfile::SystemLuaLib),
)
.expect("initial atomic System lease should close");
drop(manager);
fs::remove_dir_all(&runtime_root).expect("remove atomic System package layout");
}
#[test]
fn runtime_session_close_destroys_vm_after_manager_unlock() {
let manager = Arc::new(RuntimeSessionManager::new());
let (drop_tx, drop_rx) = mpsc::channel();
let created = insert_public_test_lease(
&manager,
"lock-free-close",
false,
drop_probed_test_lua_vm(&manager, drop_tx),
);
let lease_id = test_lease_id(&created);
manager
.close(
&lease_id,
Some("lock-free-close"),
created["generation"].as_u64(),
Some(RuntimeLeaseProfile::Public),
)
.expect("test runtime lease should close");
assert_drop_observed_after_manager_unlock(drop_rx, "close");
}
#[test]
fn runtime_session_replace_destroys_old_vm_after_manager_unlock() {
let manager = Arc::new(RuntimeSessionManager::new());
let (drop_tx, drop_rx) = mpsc::channel();
let old_created = insert_public_test_lease(
&manager,
"lock-free-replace",
false,
drop_probed_test_lua_vm(&manager, drop_tx),
);
let old_lease_id = test_lease_id(&old_created);
let replacement =
insert_public_test_lease(&manager, "lock-free-replace", true, plain_test_lua_vm());
assert_ne!(test_lease_id(&replacement), old_lease_id);
assert_drop_observed_after_manager_unlock(drop_rx, "replace");
manager
.close(
replacement["lease_id"]
.as_str()
.expect("replacement lease id"),
Some("lock-free-replace"),
replacement["generation"].as_u64(),
Some(RuntimeLeaseProfile::Public),
)
.expect("replacement test lease should close");
}
#[test]
fn runtime_session_prune_destroys_vm_after_manager_unlock() {
let manager = Arc::new(RuntimeSessionManager::new());
let (drop_tx, drop_rx) = mpsc::channel();
let created = manager
.insert(
RuntimeLeaseProfile::Public,
"lock-free-prune".to_string(),
Some(1),
false,
public_test_path_context(),
drop_probed_test_lua_vm(&manager, drop_tx),
)
.expect("finite test runtime lease should be inserted");
let lease_id = test_lease_id(&created);
let session = {
let state = manager
.lock_state()
.expect("inspect finite test runtime lease");
Arc::clone(
&state
.leases
.get(&lease_id)
.expect("finite test runtime lease entry")
.session,
)
};
{
let mut session = session
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
session.expires_at = Some(
Instant::now()
.checked_sub(Duration::from_secs(1))
.expect("expired test timestamp"),
);
}
drop(session);
let listed = manager
.list(None, Some(RuntimeLeaseProfile::Public))
.expect("runtime lease list should prune expired entries");
assert_eq!(listed["leases"], json!([]));
assert_drop_observed_after_manager_unlock(drop_rx, "prune");
}
#[test]
fn runtime_lease_cwd_error_uses_host_visible_path() {
let temp_root = std::env::temp_dir().join(format!(
"luaskills_runtime_lease_cwd_test_{}",
std::process::id()
));
if temp_root.exists() {
let _ = std::fs::remove_dir_all(&temp_root);
}
std::fs::create_dir_all(&temp_root).expect("temp root should be created");
let cwd_path = temp_root.join("not-a-directory.txt");
std::fs::write(&cwd_path, b"not a directory").expect("cwd fixture file should be written");
let lua = mlua::Lua::new();
let error =
super::LuaEngine::eval_lua_value_with_optional_cwd(&lua, "return 1", Some(&cwd_path))
.expect_err("file cwd should fail")
.to_string();
let expected_fragment = format!(
"runtime lease set cwd {}:",
render_host_visible_path(&cwd_path)
);
assert!(
error.contains(&expected_fragment),
"unexpected error: {}",
error
);
let _ = std::fs::remove_dir_all(&temp_root);
}
}