refactor: rename execution workspace to working directory
This commit is contained in:
@@ -56,7 +56,7 @@ impl std::ops::Deref for RepositorySelector {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ExecutionWorkspaceRepository {
|
||||
pub struct WorkingDirectoryRepository {
|
||||
pub id: String,
|
||||
pub provider: String,
|
||||
pub uri: String,
|
||||
@@ -81,8 +81,8 @@ pub enum DirtyStatePolicy {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ExecutionWorkspaceRequest {
|
||||
pub repository: ExecutionWorkspaceRepository,
|
||||
pub struct WorkingDirectoryRequest {
|
||||
pub repository: WorkingDirectoryRepository,
|
||||
#[serde(default)]
|
||||
pub materializer: MaterializerKind,
|
||||
#[serde(default)]
|
||||
@@ -90,7 +90,7 @@ pub struct ExecutionWorkspaceRequest {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ExecutionWorkspaceAllocationClaim {
|
||||
pub struct WorkingDirectoryAllocationClaim {
|
||||
pub allocation_id: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub relative_cwd: Option<String>,
|
||||
@@ -98,21 +98,21 @@ pub struct ExecutionWorkspaceAllocationClaim {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecutionWorkspaceStatusKind {
|
||||
pub enum WorkingDirectoryStatusKind {
|
||||
Active,
|
||||
Removed,
|
||||
CleanupPending,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ExecutionWorkspaceCleanupTarget {
|
||||
pub struct WorkingDirectoryCleanupTarget {
|
||||
pub kind: String,
|
||||
pub allocation_id: String,
|
||||
pub repository_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ExecutionWorkspaceSummary {
|
||||
pub struct WorkingDirectorySummary {
|
||||
pub allocation_id: String,
|
||||
pub repository_id: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -124,15 +124,15 @@ pub struct ExecutionWorkspaceSummary {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub resolved_tree: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cleanup_target: Option<ExecutionWorkspaceCleanupTarget>,
|
||||
pub cleanup_target: Option<WorkingDirectoryCleanupTarget>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub cleanup_policy: Option<String>,
|
||||
pub status: ExecutionWorkspaceStatusKind,
|
||||
pub status: WorkingDirectoryStatusKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ExecutionWorkspaceStatus {
|
||||
pub summary: ExecutionWorkspaceSummary,
|
||||
pub struct WorkingDirectoryStatus {
|
||||
pub summary: WorkingDirectorySummary,
|
||||
}
|
||||
|
||||
/// Canonical Runtime Worker creation request.
|
||||
@@ -141,9 +141,9 @@ pub struct ExecutionWorkspaceStatus {
|
||||
/// request is built. The request contains only durable Runtime identity inputs:
|
||||
/// a backend-decided profile selector, a previously synced ConfigBundle identity,
|
||||
/// optional initial user input that is committed in the same transaction as
|
||||
/// Worker catalog/transcript persistence, and an optional execution workspace
|
||||
/// Worker catalog/transcript persistence, and an optional working directory
|
||||
/// request that preserves RepositoryPoint-style semantics for runtime-side
|
||||
/// materialization. Browser-facing status for materialized workspaces is
|
||||
/// materialization. Browser-facing status for materialized working directories is
|
||||
/// summarized without exposing raw host paths.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CreateWorkerRequest {
|
||||
@@ -152,9 +152,9 @@ pub struct CreateWorkerRequest {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub initial_input: Option<WorkerInput>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub execution_workspace: Option<ExecutionWorkspaceRequest>,
|
||||
pub working_directory: Option<WorkingDirectoryRequest>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub execution_workspace_allocation: Option<ExecutionWorkspaceAllocationClaim>,
|
||||
pub working_directory_allocation: Option<WorkingDirectoryAllocationClaim>,
|
||||
}
|
||||
|
||||
/// Worker lifecycle status for the in-memory embedded runtime.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::catalog::{CreateWorkerRequest, ExecutionWorkspaceStatus};
|
||||
use crate::catalog::{CreateWorkerRequest, WorkingDirectoryStatus};
|
||||
use crate::error::RuntimeError;
|
||||
use crate::execution_workspace::ExecutionWorkspaceBinding;
|
||||
use crate::identity::WorkerRef;
|
||||
use crate::interaction::WorkerInput;
|
||||
#[cfg(feature = "ws-server")]
|
||||
use crate::observation::WorkerObservationEvent;
|
||||
use crate::working_directory::WorkingDirectoryBinding;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
@@ -154,7 +154,7 @@ pub struct WorkerExecutionStatus {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub binding: Option<WorkerExecutionBindingIdentity>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub execution_workspace: Option<ExecutionWorkspaceStatus>,
|
||||
pub working_directory: Option<WorkingDirectoryStatus>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub last_result: Option<WorkerExecutionResult>,
|
||||
}
|
||||
@@ -169,7 +169,7 @@ impl WorkerExecutionStatus {
|
||||
backend: WorkerExecutionBackendKind::Connected,
|
||||
run_state,
|
||||
binding: None,
|
||||
execution_workspace: None,
|
||||
working_directory: None,
|
||||
last_result: None,
|
||||
}
|
||||
}
|
||||
@@ -185,8 +185,8 @@ impl WorkerExecutionStatus {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_execution_workspace(mut self, status: ExecutionWorkspaceStatus) -> Self {
|
||||
self.execution_workspace = Some(status);
|
||||
pub fn with_working_directory(mut self, status: WorkingDirectoryStatus) -> Self {
|
||||
self.working_directory = Some(status);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ pub struct WorkerExecutionSpawnRequest {
|
||||
pub worker_ref: WorkerRef,
|
||||
pub request: CreateWorkerRequest,
|
||||
pub context: WorkerExecutionContext,
|
||||
pub execution_workspace: Option<ExecutionWorkspaceBinding>,
|
||||
pub working_directory: Option<WorkingDirectoryBinding>,
|
||||
}
|
||||
|
||||
/// Result of backend Worker spawn/initialization.
|
||||
@@ -295,7 +295,7 @@ pub enum WorkerExecutionSpawnResult {
|
||||
Connected {
|
||||
handle: WorkerExecutionHandle,
|
||||
run_state: WorkerExecutionRunState,
|
||||
execution_workspace: Option<ExecutionWorkspaceStatus>,
|
||||
working_directory: Option<WorkingDirectoryStatus>,
|
||||
},
|
||||
Rejected(WorkerExecutionResult),
|
||||
Errored(WorkerExecutionResult),
|
||||
|
||||
@@ -863,8 +863,8 @@ mod tests {
|
||||
digest: bundle.metadata.digest,
|
||||
},
|
||||
initial_input: None,
|
||||
execution_workspace: None,
|
||||
execution_workspace_allocation: None,
|
||||
working_directory: None,
|
||||
working_directory_allocation: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -879,8 +879,8 @@ mod tests {
|
||||
WorkerExecutionSpawnResult::Connected {
|
||||
handle: WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
|
||||
run_state: WorkerExecutionRunState::Idle,
|
||||
execution_workspace: request
|
||||
.execution_workspace
|
||||
working_directory: request
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.map(|binding| binding.status()),
|
||||
}
|
||||
@@ -1098,8 +1098,8 @@ mod ws_tests {
|
||||
WorkerExecutionSpawnResult::Connected {
|
||||
handle: WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
|
||||
run_state: WorkerExecutionRunState::Idle,
|
||||
execution_workspace: request
|
||||
.execution_workspace
|
||||
working_directory: request
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.map(|binding| binding.status()),
|
||||
}
|
||||
@@ -1148,8 +1148,8 @@ mod ws_tests {
|
||||
digest: bundle.metadata.digest,
|
||||
},
|
||||
initial_input: None,
|
||||
execution_workspace: None,
|
||||
execution_workspace_allocation: None,
|
||||
working_directory: None,
|
||||
working_directory_allocation: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ pub mod config_bundle;
|
||||
pub mod diagnostics;
|
||||
pub mod error;
|
||||
pub mod execution;
|
||||
pub mod execution_workspace;
|
||||
#[cfg(feature = "fs-store")]
|
||||
pub mod fs_store;
|
||||
#[cfg(feature = "http-server")]
|
||||
@@ -22,6 +21,7 @@ pub mod management;
|
||||
pub mod observation;
|
||||
mod runtime;
|
||||
pub mod worker_backend;
|
||||
pub mod working_directory;
|
||||
|
||||
#[cfg(feature = "fs-store")]
|
||||
pub use fs_store::{FsRuntimeStore, FsRuntimeStoreOptions};
|
||||
|
||||
@@ -14,13 +14,13 @@ use std::process::ExitCode;
|
||||
use std::sync::Arc;
|
||||
|
||||
use worker_runtime::error::RuntimeError;
|
||||
use worker_runtime::execution_workspace::LocalGitWorktreeMaterializer;
|
||||
use worker_runtime::fs_store::FsRuntimeStoreOptions;
|
||||
use worker_runtime::http_server::{
|
||||
RuntimeHttpServerConfig, RuntimeHttpServerError, RuntimeHttpStoreSelection,
|
||||
};
|
||||
use worker_runtime::identity::RuntimeId;
|
||||
use worker_runtime::worker_backend::{ProfileRuntimeWorkerFactory, WorkerRuntimeExecutionBackend};
|
||||
use worker_runtime::working_directory::LocalGitWorktreeMaterializer;
|
||||
use worker_runtime::{Runtime, RuntimeOptions};
|
||||
|
||||
fn main() -> ExitCode {
|
||||
@@ -81,12 +81,12 @@ fn build_runtime(config: &ProcessConfig) -> Result<Runtime, ProcessError> {
|
||||
if let Some(profile) = config.profile.clone() {
|
||||
factory = factory.with_profile(profile);
|
||||
}
|
||||
let execution_workspace_root = execution_workspace_runtime_root(config);
|
||||
let working_directory_root = working_directory_runtime_root(config);
|
||||
let backend = Arc::new(
|
||||
WorkerRuntimeExecutionBackend::new(factory)
|
||||
.map_err(ProcessError::WorkerAdapter)?
|
||||
.with_execution_workspace_materializer(LocalGitWorktreeMaterializer::new(
|
||||
execution_workspace_root,
|
||||
.with_working_directory_materializer(LocalGitWorktreeMaterializer::new(
|
||||
working_directory_root,
|
||||
)),
|
||||
);
|
||||
|
||||
@@ -109,7 +109,7 @@ fn build_runtime(config: &ProcessConfig) -> Result<Runtime, ProcessError> {
|
||||
}
|
||||
}
|
||||
|
||||
fn execution_workspace_runtime_root(config: &ProcessConfig) -> PathBuf {
|
||||
fn working_directory_runtime_root(config: &ProcessConfig) -> PathBuf {
|
||||
match &config.http.store {
|
||||
RuntimeHttpStoreSelection::Fs { root } => root.clone(),
|
||||
_ => config
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::catalog::{
|
||||
ConfigBundleRef, CreateWorkerRequest,
|
||||
ExecutionWorkspaceStatus as CatalogExecutionWorkspaceStatus, ProfileSelector, WorkerDetail,
|
||||
WorkerLifecycleAck, WorkerStatus, WorkerSummary,
|
||||
ConfigBundleRef, CreateWorkerRequest, ProfileSelector, WorkerDetail, WorkerLifecycleAck,
|
||||
WorkerStatus, WorkerSummary, WorkingDirectoryStatus as CatalogWorkingDirectoryStatus,
|
||||
};
|
||||
use crate::config_bundle::{
|
||||
ConfigBundle, ConfigBundleAvailability, ConfigBundleSummary, validate_config_bundle,
|
||||
@@ -289,18 +288,18 @@ impl Runtime {
|
||||
worker_ref: worker_ref.clone(),
|
||||
request,
|
||||
context: self.execution_context(worker_ref.clone()),
|
||||
execution_workspace: None,
|
||||
working_directory: None,
|
||||
};
|
||||
(backend, worker_ref, spawn_request)
|
||||
};
|
||||
|
||||
let spawn_result = backend.spawn_worker(spawn_request);
|
||||
let (handle, run_state, execution_workspace) = match spawn_result {
|
||||
let (handle, run_state, working_directory) = match spawn_result {
|
||||
WorkerExecutionSpawnResult::Connected {
|
||||
handle,
|
||||
run_state,
|
||||
execution_workspace,
|
||||
} => (handle, run_state, execution_workspace),
|
||||
working_directory,
|
||||
} => (handle, run_state, working_directory),
|
||||
WorkerExecutionSpawnResult::Rejected(result)
|
||||
| WorkerExecutionSpawnResult::Errored(result) => {
|
||||
self.rollback_failed_create(&worker_ref)?;
|
||||
@@ -334,7 +333,7 @@ impl Runtime {
|
||||
&worker_ref,
|
||||
handle,
|
||||
WorkerExecutionRunState::Busy,
|
||||
execution_workspace,
|
||||
working_directory,
|
||||
WorkerExecutionResult::accepted(
|
||||
WorkerExecutionOperation::Input,
|
||||
WorkerExecutionRunState::Busy,
|
||||
@@ -345,7 +344,7 @@ impl Runtime {
|
||||
&worker_ref,
|
||||
handle,
|
||||
run_state,
|
||||
execution_workspace,
|
||||
working_directory,
|
||||
WorkerExecutionResult::accepted(WorkerExecutionOperation::Spawn, run_state),
|
||||
)
|
||||
}
|
||||
@@ -441,7 +440,7 @@ impl Runtime {
|
||||
backend: WorkerExecutionBackendKind::Connected,
|
||||
run_state: dispatch_result.run_state,
|
||||
binding: worker.execution.binding.clone(),
|
||||
execution_workspace: worker.execution.execution_workspace.clone(),
|
||||
working_directory: worker.execution.working_directory.clone(),
|
||||
last_result: Some(dispatch_result),
|
||||
};
|
||||
worker.transcript.push(TranscriptEntry {
|
||||
@@ -491,7 +490,7 @@ impl Runtime {
|
||||
worker_ref: &WorkerRef,
|
||||
handle: WorkerExecutionHandle,
|
||||
run_state: WorkerExecutionRunState,
|
||||
execution_workspace: Option<CatalogExecutionWorkspaceStatus>,
|
||||
working_directory: Option<CatalogWorkingDirectoryStatus>,
|
||||
result: WorkerExecutionResult,
|
||||
) -> Result<WorkerDetail, RuntimeError> {
|
||||
let mut state = self.lock()?;
|
||||
@@ -501,8 +500,8 @@ impl Runtime {
|
||||
let worker = state.worker_mut(worker_ref)?;
|
||||
worker.execution_handle = Some(handle);
|
||||
let mut execution = WorkerExecutionStatus::connected(run_state).with_binding(binding);
|
||||
if let Some(status) = execution_workspace {
|
||||
execution = execution.with_execution_workspace(status);
|
||||
if let Some(status) = working_directory {
|
||||
execution = execution.with_working_directory(status);
|
||||
}
|
||||
worker.execution = execution.with_result(result);
|
||||
worker.detail(&runtime_id)
|
||||
@@ -538,7 +537,7 @@ impl Runtime {
|
||||
backend: WorkerExecutionBackendKind::Connected,
|
||||
run_state: result.run_state,
|
||||
binding: worker.execution.binding.clone(),
|
||||
execution_workspace: worker.execution.execution_workspace.clone(),
|
||||
working_directory: worker.execution.working_directory.clone(),
|
||||
last_result: Some(result),
|
||||
};
|
||||
state.persist_worker(&worker_ref.worker_id)?;
|
||||
@@ -1593,8 +1592,8 @@ mod tests {
|
||||
digest: bundle.metadata.digest,
|
||||
},
|
||||
initial_input: None,
|
||||
execution_workspace: None,
|
||||
execution_workspace_allocation: None,
|
||||
working_directory: None,
|
||||
working_directory_allocation: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1660,8 +1659,8 @@ mod tests {
|
||||
WorkerExecutionSpawnResult::Connected {
|
||||
handle: WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
|
||||
run_state: WorkerExecutionRunState::Idle,
|
||||
execution_workspace: request
|
||||
.execution_workspace
|
||||
working_directory: request
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.map(|binding| binding.status()),
|
||||
}
|
||||
@@ -1935,8 +1934,8 @@ mod tests {
|
||||
WorkerExecutionSpawnResult::Connected {
|
||||
handle: WorkerExecutionHandle::new(request.worker_ref, self.backend_id()),
|
||||
run_state: WorkerExecutionRunState::Idle,
|
||||
execution_workspace: request
|
||||
.execution_workspace
|
||||
working_directory: request
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.map(|binding| binding.status()),
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@ use crate::execution::{
|
||||
WorkerExecutionBackend, WorkerExecutionHandle, WorkerExecutionOperation, WorkerExecutionResult,
|
||||
WorkerExecutionRunState, WorkerExecutionSpawnRequest, WorkerExecutionSpawnResult,
|
||||
};
|
||||
use crate::execution_workspace::{ExecutionWorkspaceBinding, ExecutionWorkspaceMaterializer};
|
||||
use crate::interaction::{WorkerInput, WorkerInputKind};
|
||||
use crate::working_directory::{WorkingDirectoryBinding, WorkingDirectoryMaterializer};
|
||||
use async_trait::async_trait;
|
||||
use manifest::paths;
|
||||
use protocol::{Method, Segment, WorkerStatus};
|
||||
@@ -162,12 +162,12 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory {
|
||||
let worker_name = Self::runtime_worker_name(&request);
|
||||
let profile = self.runtime_profile(&request);
|
||||
let workspace_root = request
|
||||
.execution_workspace
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.map(|binding| binding.workspace_root().to_path_buf())
|
||||
.unwrap_or_else(|| self.workspace_root.clone());
|
||||
let cwd = request
|
||||
.execution_workspace
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.map(|binding| binding.cwd().to_path_buf())
|
||||
.unwrap_or_else(|| self.cwd.clone());
|
||||
@@ -210,14 +210,14 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory {
|
||||
struct RuntimeWorkerExecution {
|
||||
handle: WorkerHandle,
|
||||
busy: Arc<AtomicBool>,
|
||||
execution_workspace: Option<ExecutionWorkspaceBinding>,
|
||||
working_directory: Option<WorkingDirectoryBinding>,
|
||||
}
|
||||
|
||||
/// `worker-runtime` execution backend backed by real `worker` crate Workers.
|
||||
pub struct WorkerRuntimeExecutionBackend<F = ProfileRuntimeWorkerFactory> {
|
||||
backend_id: String,
|
||||
factory: Arc<F>,
|
||||
execution_workspace_materializer: Option<Arc<dyn ExecutionWorkspaceMaterializer>>,
|
||||
working_directory_materializer: Option<Arc<dyn WorkingDirectoryMaterializer>>,
|
||||
runtime: Mutex<Option<Runtime>>,
|
||||
workers: Mutex<HashMap<crate::identity::WorkerRef, RuntimeWorkerExecution>>,
|
||||
}
|
||||
@@ -241,7 +241,7 @@ where
|
||||
Ok(Self {
|
||||
backend_id: DEFAULT_BACKEND_ID.to_string(),
|
||||
factory: Arc::new(factory),
|
||||
execution_workspace_materializer: None,
|
||||
working_directory_materializer: None,
|
||||
runtime: Mutex::new(Some(runtime)),
|
||||
workers: Mutex::new(HashMap::new()),
|
||||
})
|
||||
@@ -252,11 +252,11 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_execution_workspace_materializer(
|
||||
pub fn with_working_directory_materializer(
|
||||
mut self,
|
||||
materializer: impl ExecutionWorkspaceMaterializer,
|
||||
materializer: impl WorkingDirectoryMaterializer,
|
||||
) -> Self {
|
||||
self.execution_workspace_materializer = Some(Arc::new(materializer));
|
||||
self.working_directory_materializer = Some(Arc::new(materializer));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -374,26 +374,26 @@ where
|
||||
}
|
||||
|
||||
let mut request = request;
|
||||
let execution_workspace = match (
|
||||
request.request.execution_workspace.as_ref(),
|
||||
request.request.execution_workspace_allocation.as_ref(),
|
||||
let working_directory = match (
|
||||
request.request.working_directory.as_ref(),
|
||||
request.request.working_directory_allocation.as_ref(),
|
||||
) {
|
||||
(Some(_), Some(_)) => {
|
||||
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
||||
WorkerExecutionOperation::Spawn,
|
||||
"worker spawn cannot specify both execution_workspace and execution_workspace_allocation",
|
||||
"worker spawn cannot specify both working_directory and working_directory_allocation",
|
||||
));
|
||||
}
|
||||
(Some(workspace_request), None) => {
|
||||
let Some(materializer) = self.execution_workspace_materializer.as_ref() else {
|
||||
let Some(materializer) = self.working_directory_materializer.as_ref() else {
|
||||
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
||||
WorkerExecutionOperation::Spawn,
|
||||
"execution workspace materialization requested, but no materializer is configured for this runtime backend",
|
||||
"working directory materialization requested, but no materializer is configured for this runtime backend",
|
||||
));
|
||||
};
|
||||
match materializer.materialize(&request.worker_ref, workspace_request) {
|
||||
Ok(binding) => {
|
||||
request.execution_workspace = Some(binding.clone());
|
||||
request.working_directory = Some(binding.clone());
|
||||
Some(binding)
|
||||
}
|
||||
Err(error) => {
|
||||
@@ -407,10 +407,10 @@ where
|
||||
}
|
||||
}
|
||||
(None, Some(allocation)) => {
|
||||
let Some(materializer) = self.execution_workspace_materializer.as_ref() else {
|
||||
let Some(materializer) = self.working_directory_materializer.as_ref() else {
|
||||
return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected(
|
||||
WorkerExecutionOperation::Spawn,
|
||||
"execution workspace allocation requested, but no materializer is configured for this runtime backend",
|
||||
"working directory allocation requested, but no materializer is configured for this runtime backend",
|
||||
));
|
||||
};
|
||||
match materializer.bind_allocation(
|
||||
@@ -418,7 +418,7 @@ where
|
||||
allocation.relative_cwd.as_deref(),
|
||||
) {
|
||||
Ok(binding) => {
|
||||
request.execution_workspace = Some(binding.clone());
|
||||
request.working_directory = Some(binding.clone());
|
||||
Some(binding)
|
||||
}
|
||||
Err(error) => {
|
||||
@@ -444,8 +444,8 @@ where
|
||||
Ok(handle) => handle,
|
||||
Err(message) => {
|
||||
if let (Some(materializer), Some(binding)) = (
|
||||
self.execution_workspace_materializer.as_ref(),
|
||||
execution_workspace.as_ref(),
|
||||
self.working_directory_materializer.as_ref(),
|
||||
working_directory.as_ref(),
|
||||
) {
|
||||
let _ = materializer.cleanup(binding);
|
||||
}
|
||||
@@ -494,14 +494,14 @@ where
|
||||
RuntimeWorkerExecution {
|
||||
handle,
|
||||
busy,
|
||||
execution_workspace: execution_workspace.clone(),
|
||||
working_directory: working_directory.clone(),
|
||||
},
|
||||
);
|
||||
|
||||
WorkerExecutionSpawnResult::Connected {
|
||||
handle: WorkerExecutionHandle::new(worker_ref, self.backend_id()),
|
||||
run_state: WorkerExecutionRunState::Idle,
|
||||
execution_workspace: execution_workspace.map(|binding| binding.status()),
|
||||
working_directory: working_directory.map(|binding| binding.status()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,8 +592,8 @@ where
|
||||
WorkerExecutionRunState::Stopped,
|
||||
);
|
||||
if let (Some(materializer), Some(binding)) = (
|
||||
self.execution_workspace_materializer.as_ref(),
|
||||
execution.execution_workspace.as_ref(),
|
||||
self.working_directory_materializer.as_ref(),
|
||||
execution.working_directory.as_ref(),
|
||||
) {
|
||||
let _ = materializer.cleanup(binding);
|
||||
}
|
||||
@@ -627,13 +627,13 @@ mod tests {
|
||||
|
||||
use crate::Runtime as EmbeddedRuntime;
|
||||
use crate::catalog::{
|
||||
ConfigBundleRef, CreateWorkerRequest, DirtyStatePolicy, ExecutionWorkspaceRepository,
|
||||
ExecutionWorkspaceRequest, MaterializerKind, ProfileSelector, RepositorySelector,
|
||||
ConfigBundleRef, CreateWorkerRequest, DirtyStatePolicy, MaterializerKind, ProfileSelector,
|
||||
RepositorySelector, WorkingDirectoryRepository, WorkingDirectoryRequest,
|
||||
};
|
||||
use crate::execution_workspace::LocalGitWorktreeMaterializer;
|
||||
use crate::identity::RuntimeId;
|
||||
use crate::management::RuntimeOptions;
|
||||
use crate::observation::{TranscriptQuery, TranscriptRole};
|
||||
use crate::working_directory::LocalGitWorktreeMaterializer;
|
||||
use async_trait::async_trait;
|
||||
use futures::Stream;
|
||||
use llm_engine::Engine;
|
||||
@@ -715,7 +715,7 @@ mod tests {
|
||||
FsWorkerStore::new(&self.worker_metadata_dir).map_err(|err| err.to_string())?,
|
||||
);
|
||||
let cwd = request
|
||||
.execution_workspace
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.map(|binding| binding.cwd().to_path_buf())
|
||||
.unwrap_or_else(|| self.cwd.clone());
|
||||
@@ -780,8 +780,8 @@ mod tests {
|
||||
digest: bundle.metadata.digest,
|
||||
},
|
||||
initial_input: None,
|
||||
execution_workspace: None,
|
||||
execution_workspace_allocation: None,
|
||||
working_directory: None,
|
||||
working_directory_allocation: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,9 +809,9 @@ mod tests {
|
||||
dir
|
||||
}
|
||||
|
||||
fn execution_workspace_request(repo: &std::path::Path) -> ExecutionWorkspaceRequest {
|
||||
ExecutionWorkspaceRequest {
|
||||
repository: ExecutionWorkspaceRepository {
|
||||
fn working_directory_request(repo: &std::path::Path) -> WorkingDirectoryRequest {
|
||||
WorkingDirectoryRequest {
|
||||
repository: WorkingDirectoryRepository {
|
||||
id: "repo-main".to_string(),
|
||||
provider: "git".to_string(),
|
||||
uri: ".".to_string(),
|
||||
@@ -920,7 +920,7 @@ mod tests {
|
||||
};
|
||||
let backend = WorkerRuntimeExecutionBackend::new(factory)
|
||||
.unwrap()
|
||||
.with_execution_workspace_materializer(LocalGitWorktreeMaterializer::new(
|
||||
.with_working_directory_materializer(LocalGitWorktreeMaterializer::new(
|
||||
runtime_base.path(),
|
||||
));
|
||||
let runtime = EmbeddedRuntime::with_execution_backend(
|
||||
@@ -933,11 +933,11 @@ mod tests {
|
||||
.unwrap();
|
||||
runtime.store_config_bundle(test_bundle()).unwrap();
|
||||
let mut request = create_request("chat");
|
||||
request.execution_workspace = Some(execution_workspace_request(repo.path()));
|
||||
request.working_directory = Some(working_directory_request(repo.path()));
|
||||
|
||||
let detail = runtime.create_worker(request).unwrap();
|
||||
|
||||
assert!(detail.execution.execution_workspace.is_some());
|
||||
assert!(detail.execution.working_directory.is_some());
|
||||
let cwds = observed_cwds.lock().unwrap();
|
||||
assert_eq!(cwds.len(), 1);
|
||||
let cwd = &cwds[0];
|
||||
|
||||
+155
-169
@@ -1,7 +1,6 @@
|
||||
use crate::catalog::{
|
||||
DirtyStatePolicy, ExecutionWorkspaceCleanupTarget, ExecutionWorkspaceRequest,
|
||||
ExecutionWorkspaceStatus, ExecutionWorkspaceStatusKind, ExecutionWorkspaceSummary,
|
||||
MaterializerKind,
|
||||
DirtyStatePolicy, MaterializerKind, WorkingDirectoryCleanupTarget, WorkingDirectoryRequest,
|
||||
WorkingDirectoryStatus, WorkingDirectoryStatusKind, WorkingDirectorySummary,
|
||||
};
|
||||
use crate::identity::WorkerRef;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -11,12 +10,12 @@ use std::process::Command;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
const EXECUTION_WORKSPACES_DIR: &str = "execution-workspaces";
|
||||
const WORKING_DIRECTORIES_DIR: &str = "working-directories";
|
||||
const MATERIALIZATION_RECORD: &str = "materialization.json";
|
||||
static NEXT_ALLOCATION_SEQUENCE: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ExecutionWorkspaceEvidence {
|
||||
pub struct WorkingDirectoryEvidence {
|
||||
pub repository_id: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub requested_selector: Option<String>,
|
||||
@@ -28,20 +27,20 @@ pub struct ExecutionWorkspaceEvidence {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ExecutionWorkspaceAllocation {
|
||||
pub struct WorkingDirectoryAllocation {
|
||||
pub id: String,
|
||||
pub repository_id: String,
|
||||
pub materializer_kind: MaterializerKind,
|
||||
pub dirty_state_policy: DirtyStatePolicy,
|
||||
pub evidence: ExecutionWorkspaceEvidence,
|
||||
pub cleanup_target: ExecutionWorkspaceCleanupTarget,
|
||||
pub evidence: WorkingDirectoryEvidence,
|
||||
pub cleanup_target: WorkingDirectoryCleanupTarget,
|
||||
pub cleanup_policy: String,
|
||||
pub status: ExecutionWorkspaceStatusKind,
|
||||
pub status: WorkingDirectoryStatusKind,
|
||||
}
|
||||
|
||||
impl ExecutionWorkspaceAllocation {
|
||||
pub fn status_summary(&self) -> ExecutionWorkspaceSummary {
|
||||
ExecutionWorkspaceSummary {
|
||||
impl WorkingDirectoryAllocation {
|
||||
pub fn status_summary(&self) -> WorkingDirectorySummary {
|
||||
WorkingDirectorySummary {
|
||||
allocation_id: self.id.clone(),
|
||||
repository_id: self.repository_id.clone(),
|
||||
requested_selector: self.evidence.requested_selector.clone(),
|
||||
@@ -57,15 +56,15 @@ impl ExecutionWorkspaceAllocation {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ExecutionWorkspaceBinding {
|
||||
pub allocation: ExecutionWorkspaceAllocation,
|
||||
pub struct WorkingDirectoryBinding {
|
||||
pub allocation: WorkingDirectoryAllocation,
|
||||
pub workspace_root: PathBuf,
|
||||
pub cwd: PathBuf,
|
||||
allocation_root: PathBuf,
|
||||
source_repository_path: PathBuf,
|
||||
}
|
||||
|
||||
impl ExecutionWorkspaceBinding {
|
||||
impl WorkingDirectoryBinding {
|
||||
pub fn workspace_root(&self) -> &Path {
|
||||
&self.workspace_root
|
||||
}
|
||||
@@ -82,20 +81,20 @@ impl ExecutionWorkspaceBinding {
|
||||
&self.source_repository_path
|
||||
}
|
||||
|
||||
pub fn status(&self) -> ExecutionWorkspaceStatus {
|
||||
ExecutionWorkspaceStatus {
|
||||
pub fn status(&self) -> WorkingDirectoryStatus {
|
||||
WorkingDirectoryStatus {
|
||||
summary: self.allocation.status_summary(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ExecutionWorkspaceDiagnostic {
|
||||
pub struct WorkingDirectoryDiagnostic {
|
||||
pub code: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
impl ExecutionWorkspaceDiagnostic {
|
||||
impl WorkingDirectoryDiagnostic {
|
||||
fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
code: code.into(),
|
||||
@@ -104,50 +103,45 @@ impl ExecutionWorkspaceDiagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ExecutionWorkspaceDiagnostic {
|
||||
impl std::fmt::Display for WorkingDirectoryDiagnostic {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}: {}", self.code, self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ExecutionWorkspaceDiagnostic {}
|
||||
impl std::error::Error for WorkingDirectoryDiagnostic {}
|
||||
|
||||
pub trait ExecutionWorkspaceMaterializer: Send + Sync + 'static {
|
||||
pub trait WorkingDirectoryMaterializer: Send + Sync + 'static {
|
||||
fn materialize(
|
||||
&self,
|
||||
worker_ref: &WorkerRef,
|
||||
request: &ExecutionWorkspaceRequest,
|
||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic>;
|
||||
request: &WorkingDirectoryRequest,
|
||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic>;
|
||||
|
||||
fn preallocate(
|
||||
&self,
|
||||
request: &ExecutionWorkspaceRequest,
|
||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic>;
|
||||
request: &WorkingDirectoryRequest,
|
||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic>;
|
||||
|
||||
fn bind_allocation(
|
||||
&self,
|
||||
allocation_id: &str,
|
||||
relative_cwd: Option<&str>,
|
||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic>;
|
||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic>;
|
||||
|
||||
fn list_allocations(
|
||||
&self,
|
||||
) -> Result<Vec<ExecutionWorkspaceStatus>, ExecutionWorkspaceDiagnostic>;
|
||||
fn list_allocations(&self) -> Result<Vec<WorkingDirectoryStatus>, WorkingDirectoryDiagnostic>;
|
||||
|
||||
fn allocation_status(
|
||||
&self,
|
||||
allocation_id: &str,
|
||||
) -> Result<ExecutionWorkspaceStatus, ExecutionWorkspaceDiagnostic>;
|
||||
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic>;
|
||||
|
||||
fn cleanup_allocation(
|
||||
&self,
|
||||
allocation_id: &str,
|
||||
) -> Result<ExecutionWorkspaceStatus, ExecutionWorkspaceDiagnostic>;
|
||||
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic>;
|
||||
|
||||
fn cleanup(
|
||||
&self,
|
||||
binding: &ExecutionWorkspaceBinding,
|
||||
) -> Result<(), ExecutionWorkspaceDiagnostic>;
|
||||
fn cleanup(&self, binding: &WorkingDirectoryBinding) -> Result<(), WorkingDirectoryDiagnostic>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -177,30 +171,30 @@ impl LocalGitWorktreeMaterializer {
|
||||
|
||||
fn allocation_root(&self, allocation_id: &str) -> PathBuf {
|
||||
self.runtime_root
|
||||
.join(EXECUTION_WORKSPACES_DIR)
|
||||
.join(WORKING_DIRECTORIES_DIR)
|
||||
.join(allocation_id)
|
||||
}
|
||||
|
||||
fn write_record(
|
||||
&self,
|
||||
binding: &ExecutionWorkspaceBinding,
|
||||
) -> Result<(), ExecutionWorkspaceDiagnostic> {
|
||||
let record = ExecutionWorkspaceMaterializationRecord {
|
||||
binding: &WorkingDirectoryBinding,
|
||||
) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||
let record = WorkingDirectoryMaterializationRecord {
|
||||
allocation: binding.allocation.clone(),
|
||||
workspace_root: binding.workspace_root.clone(),
|
||||
source_repository_path: binding.source_repository_path.clone(),
|
||||
};
|
||||
let path = binding.allocation_root.join(MATERIALIZATION_RECORD);
|
||||
let raw = serde_json::to_vec_pretty(&record).map_err(|error| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_record_serialize_failed",
|
||||
format!("failed to serialize execution workspace record: {error}"),
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_record_serialize_failed",
|
||||
format!("failed to serialize working directory record: {error}"),
|
||||
)
|
||||
})?;
|
||||
fs::write(&path, raw).map_err(|_| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_record_write_failed",
|
||||
"failed to write execution workspace record; backend-private path details were omitted",
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_record_write_failed",
|
||||
"failed to write working directory record; backend-private path details were omitted",
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -208,22 +202,22 @@ impl LocalGitWorktreeMaterializer {
|
||||
fn read_binding(
|
||||
&self,
|
||||
allocation_id: &str,
|
||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic> {
|
||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||
let allocation_root = self.allocation_root(allocation_id);
|
||||
let path = allocation_root.join(MATERIALIZATION_RECORD);
|
||||
let raw = fs::read(&path).map_err(|_| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_allocation_not_found",
|
||||
"execution workspace allocation was not found",
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_allocation_not_found",
|
||||
"working directory allocation was not found",
|
||||
)
|
||||
})?;
|
||||
let record: ExecutionWorkspaceMaterializationRecord = serde_json::from_slice(&raw).map_err(|_| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_record_invalid",
|
||||
"execution workspace allocation record is invalid; backend-private path details were omitted",
|
||||
let record: WorkingDirectoryMaterializationRecord = serde_json::from_slice(&raw).map_err(|_| {
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_record_invalid",
|
||||
"working directory allocation record is invalid; backend-private path details were omitted",
|
||||
)
|
||||
})?;
|
||||
Ok(ExecutionWorkspaceBinding {
|
||||
Ok(WorkingDirectoryBinding {
|
||||
allocation: record.allocation,
|
||||
workspace_root: record.workspace_root.clone(),
|
||||
cwd: record.workspace_root,
|
||||
@@ -235,34 +229,34 @@ impl LocalGitWorktreeMaterializer {
|
||||
fn materialize_with_allocation_id(
|
||||
&self,
|
||||
allocation_id: String,
|
||||
request: &ExecutionWorkspaceRequest,
|
||||
request: &WorkingDirectoryRequest,
|
||||
cleanup_policy: &str,
|
||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic> {
|
||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||
validate_allocation_id(&allocation_id)?;
|
||||
if request.materializer != MaterializerKind::LocalGitWorktree {
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_materializer_unsupported",
|
||||
"only local_git_worktree execution workspace materialization is supported in v0",
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_materializer_unsupported",
|
||||
"only local_git_worktree working directory materialization is supported in v0",
|
||||
));
|
||||
}
|
||||
if request.repository.provider != "git" {
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_repository_provider_unsupported",
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_repository_provider_unsupported",
|
||||
format!(
|
||||
"repository provider `{}` is not supported by the v0 execution workspace materializer",
|
||||
"repository provider `{}` is not supported by the v0 working directory materializer",
|
||||
request.repository.provider
|
||||
),
|
||||
));
|
||||
}
|
||||
if request.dirty_state_policy != DirtyStatePolicy::CleanPointOnly {
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_dirty_policy_unsupported",
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_dirty_policy_unsupported",
|
||||
"only clean_point_only dirty-state policy is supported in v0",
|
||||
));
|
||||
}
|
||||
if is_remote_uri(&request.repository.uri) {
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_remote_repository_unsupported",
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_remote_repository_unsupported",
|
||||
"remote repository URI materialization is not implemented in v0",
|
||||
));
|
||||
}
|
||||
@@ -275,17 +269,17 @@ impl LocalGitWorktreeMaterializer {
|
||||
let source_root = git_stdout(&source_path, ["rev-parse", "--show-toplevel"])
|
||||
.map(|value| PathBuf::from(value.trim()))
|
||||
.map_err(|_| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_git_repository_unavailable",
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_git_repository_unavailable",
|
||||
"configured local repository is not an available Git worktree; backend-private path details were omitted",
|
||||
)
|
||||
})?;
|
||||
|
||||
let status = git_stdout(&source_root, ["status", "--porcelain"])?;
|
||||
if !status.trim().is_empty() {
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_dirty_source_rejected",
|
||||
"clean_point_only execution workspace materialization rejects dirty source repository state",
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_dirty_source_rejected",
|
||||
"clean_point_only working directory materialization rejects dirty source repository state",
|
||||
));
|
||||
}
|
||||
|
||||
@@ -310,21 +304,21 @@ impl LocalGitWorktreeMaterializer {
|
||||
.join("root")
|
||||
.join(sanitize_path_component(&request.repository.id));
|
||||
if workspace_root.exists() {
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_allocation_exists",
|
||||
"execution workspace allocation target already exists; cleanup or choose a new allocation",
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_allocation_exists",
|
||||
"working directory allocation target already exists; cleanup or choose a new allocation",
|
||||
));
|
||||
}
|
||||
fs::create_dir_all(workspace_root.parent().ok_or_else(|| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_invalid_target",
|
||||
"execution workspace allocation target has no parent directory",
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_invalid_target",
|
||||
"working directory allocation target has no parent directory",
|
||||
)
|
||||
})?)
|
||||
.map_err(|_| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_create_failed",
|
||||
"failed to create execution workspace allocation directory; backend-private path details were omitted",
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_create_failed",
|
||||
"failed to create working directory allocation directory; backend-private path details were omitted",
|
||||
)
|
||||
})?;
|
||||
|
||||
@@ -340,12 +334,12 @@ impl LocalGitWorktreeMaterializer {
|
||||
],
|
||||
)?;
|
||||
|
||||
let allocation = ExecutionWorkspaceAllocation {
|
||||
let allocation = WorkingDirectoryAllocation {
|
||||
id: allocation_id.clone(),
|
||||
repository_id: request.repository.id.clone(),
|
||||
materializer_kind: MaterializerKind::LocalGitWorktree,
|
||||
dirty_state_policy: DirtyStatePolicy::CleanPointOnly,
|
||||
evidence: ExecutionWorkspaceEvidence {
|
||||
evidence: WorkingDirectoryEvidence {
|
||||
repository_id: request.repository.id.clone(),
|
||||
requested_selector: request
|
||||
.repository
|
||||
@@ -357,15 +351,15 @@ impl LocalGitWorktreeMaterializer {
|
||||
materializer_kind: MaterializerKind::LocalGitWorktree,
|
||||
dirty_state_policy: DirtyStatePolicy::CleanPointOnly,
|
||||
},
|
||||
cleanup_target: ExecutionWorkspaceCleanupTarget {
|
||||
cleanup_target: WorkingDirectoryCleanupTarget {
|
||||
kind: "git_worktree".to_string(),
|
||||
allocation_id,
|
||||
repository_id: request.repository.id.clone(),
|
||||
},
|
||||
cleanup_policy: cleanup_policy.to_string(),
|
||||
status: ExecutionWorkspaceStatusKind::Active,
|
||||
status: WorkingDirectoryStatusKind::Active,
|
||||
};
|
||||
let binding = ExecutionWorkspaceBinding {
|
||||
let binding = WorkingDirectoryBinding {
|
||||
allocation,
|
||||
workspace_root: workspace_root.clone(),
|
||||
cwd: workspace_root,
|
||||
@@ -377,20 +371,20 @@ impl LocalGitWorktreeMaterializer {
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||
impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer {
|
||||
fn materialize(
|
||||
&self,
|
||||
worker_ref: &WorkerRef,
|
||||
request: &ExecutionWorkspaceRequest,
|
||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic> {
|
||||
request: &WorkingDirectoryRequest,
|
||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||
let allocation_id = Self::allocation_id(worker_ref, &request.repository.id);
|
||||
self.materialize_with_allocation_id(allocation_id, request, "remove_on_worker_stop")
|
||||
}
|
||||
|
||||
fn preallocate(
|
||||
&self,
|
||||
request: &ExecutionWorkspaceRequest,
|
||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic> {
|
||||
request: &WorkingDirectoryRequest,
|
||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||
let allocation_id = next_allocation_id(&request.repository.id);
|
||||
self.materialize_with_allocation_id(allocation_id, request, "manual_or_worker_stop")
|
||||
}
|
||||
@@ -399,30 +393,28 @@ impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||
&self,
|
||||
allocation_id: &str,
|
||||
relative_cwd: Option<&str>,
|
||||
) -> Result<ExecutionWorkspaceBinding, ExecutionWorkspaceDiagnostic> {
|
||||
) -> Result<WorkingDirectoryBinding, WorkingDirectoryDiagnostic> {
|
||||
validate_allocation_id(allocation_id)?;
|
||||
let binding = self.read_binding(allocation_id)?;
|
||||
if binding.allocation.status != ExecutionWorkspaceStatusKind::Active {
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_not_active",
|
||||
"execution workspace allocation is not active",
|
||||
if binding.allocation.status != WorkingDirectoryStatusKind::Active {
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_not_active",
|
||||
"working directory allocation is not active",
|
||||
));
|
||||
}
|
||||
let cwd = validate_relative_cwd(binding.workspace_root(), relative_cwd)?;
|
||||
Ok(ExecutionWorkspaceBinding { cwd, ..binding })
|
||||
Ok(WorkingDirectoryBinding { cwd, ..binding })
|
||||
}
|
||||
|
||||
fn list_allocations(
|
||||
&self,
|
||||
) -> Result<Vec<ExecutionWorkspaceStatus>, ExecutionWorkspaceDiagnostic> {
|
||||
let root = self.runtime_root.join(EXECUTION_WORKSPACES_DIR);
|
||||
fn list_allocations(&self) -> Result<Vec<WorkingDirectoryStatus>, WorkingDirectoryDiagnostic> {
|
||||
let root = self.runtime_root.join(WORKING_DIRECTORIES_DIR);
|
||||
let entries = match fs::read_dir(&root) {
|
||||
Ok(entries) => entries,
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
|
||||
Err(_) => {
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_list_failed",
|
||||
"failed to list execution workspace allocations; backend-private path details were omitted",
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_list_failed",
|
||||
"failed to list working directory allocations; backend-private path details were omitted",
|
||||
));
|
||||
}
|
||||
};
|
||||
@@ -451,7 +443,7 @@ impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||
fn allocation_status(
|
||||
&self,
|
||||
allocation_id: &str,
|
||||
) -> Result<ExecutionWorkspaceStatus, ExecutionWorkspaceDiagnostic> {
|
||||
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||
validate_allocation_id(allocation_id)?;
|
||||
Ok(self.read_binding(allocation_id)?.status())
|
||||
}
|
||||
@@ -459,34 +451,31 @@ impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||
fn cleanup_allocation(
|
||||
&self,
|
||||
allocation_id: &str,
|
||||
) -> Result<ExecutionWorkspaceStatus, ExecutionWorkspaceDiagnostic> {
|
||||
) -> Result<WorkingDirectoryStatus, WorkingDirectoryDiagnostic> {
|
||||
validate_allocation_id(allocation_id)?;
|
||||
let binding = self.read_binding(allocation_id)?;
|
||||
self.cleanup(&binding)?;
|
||||
self.allocation_status(allocation_id)
|
||||
}
|
||||
|
||||
fn cleanup(
|
||||
&self,
|
||||
binding: &ExecutionWorkspaceBinding,
|
||||
) -> Result<(), ExecutionWorkspaceDiagnostic> {
|
||||
fn cleanup(&self, binding: &WorkingDirectoryBinding) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||
let mut allocation = binding.allocation.clone();
|
||||
let allocation_root = binding.allocation_root.canonicalize().map_err(|_| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_cleanup_target_invalid",
|
||||
"execution workspace allocation root is unavailable; backend-private path details were omitted",
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_cleanup_target_invalid",
|
||||
"working directory allocation root is unavailable; backend-private path details were omitted",
|
||||
)
|
||||
})?;
|
||||
let workspace_root = binding.workspace_root.canonicalize().map_err(|_| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_cleanup_target_invalid",
|
||||
"execution workspace root is unavailable; backend-private path details were omitted",
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_cleanup_target_invalid",
|
||||
"working directory root is unavailable; backend-private path details were omitted",
|
||||
)
|
||||
})?;
|
||||
if !workspace_root.starts_with(&allocation_root) {
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_cleanup_escape_rejected",
|
||||
"execution workspace cleanup target is outside the allocation root",
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_cleanup_escape_rejected",
|
||||
"working directory cleanup target is outside the allocation root",
|
||||
));
|
||||
}
|
||||
let workspace_root_arg = path_str(&workspace_root)?;
|
||||
@@ -497,9 +486,9 @@ impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||
.or_else(|_| {
|
||||
if workspace_root.exists() {
|
||||
fs::remove_dir_all(&workspace_root).map_err(|_| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_cleanup_failed",
|
||||
"failed to remove execution workspace; backend-private path details were omitted",
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_cleanup_failed",
|
||||
"failed to remove working directory; backend-private path details were omitted",
|
||||
)
|
||||
})
|
||||
} else {
|
||||
@@ -507,11 +496,11 @@ impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||
}
|
||||
});
|
||||
allocation.status = if remove_result.is_ok() {
|
||||
ExecutionWorkspaceStatusKind::Removed
|
||||
WorkingDirectoryStatusKind::Removed
|
||||
} else {
|
||||
ExecutionWorkspaceStatusKind::CleanupPending
|
||||
WorkingDirectoryStatusKind::CleanupPending
|
||||
};
|
||||
let updated = ExecutionWorkspaceBinding {
|
||||
let updated = WorkingDirectoryBinding {
|
||||
allocation,
|
||||
workspace_root: binding.workspace_root.clone(),
|
||||
cwd: binding.cwd.clone(),
|
||||
@@ -524,16 +513,13 @@ impl ExecutionWorkspaceMaterializer for LocalGitWorktreeMaterializer {
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
struct ExecutionWorkspaceMaterializationRecord {
|
||||
allocation: ExecutionWorkspaceAllocation,
|
||||
struct WorkingDirectoryMaterializationRecord {
|
||||
allocation: WorkingDirectoryAllocation,
|
||||
workspace_root: PathBuf,
|
||||
source_repository_path: PathBuf,
|
||||
}
|
||||
|
||||
fn git_stdout<'a, I>(
|
||||
repository_path: &Path,
|
||||
args: I,
|
||||
) -> Result<String, ExecutionWorkspaceDiagnostic>
|
||||
fn git_stdout<'a, I>(repository_path: &Path, args: I) -> Result<String, WorkingDirectoryDiagnostic>
|
||||
where
|
||||
I: IntoIterator<Item = &'a str>,
|
||||
{
|
||||
@@ -543,32 +529,32 @@ where
|
||||
.args(args)
|
||||
.output()
|
||||
.map_err(|_| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_git_unavailable",
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_git_unavailable",
|
||||
"Git command could not be executed; backend-private path details were omitted",
|
||||
)
|
||||
})?;
|
||||
if !output.status.success() {
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_git_failed",
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_git_failed",
|
||||
"Git command failed; backend-private path details were omitted",
|
||||
));
|
||||
}
|
||||
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
fn git_status<'a, I>(repository_path: &Path, args: I) -> Result<(), ExecutionWorkspaceDiagnostic>
|
||||
fn git_status<'a, I>(repository_path: &Path, args: I) -> Result<(), WorkingDirectoryDiagnostic>
|
||||
where
|
||||
I: IntoIterator<Item = &'a str>,
|
||||
{
|
||||
git_stdout(repository_path, args).map(|_| ())
|
||||
}
|
||||
|
||||
fn path_str(path: &Path) -> Result<String, ExecutionWorkspaceDiagnostic> {
|
||||
fn path_str(path: &Path) -> Result<String, WorkingDirectoryDiagnostic> {
|
||||
path.to_str().map(ToString::to_string).ok_or_else(|| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_non_utf8_path",
|
||||
"execution workspace path is not valid UTF-8; backend-private path details were omitted",
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_non_utf8_path",
|
||||
"working directory path is not valid UTF-8; backend-private path details were omitted",
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -612,7 +598,7 @@ fn next_allocation_id(repository_id: &str) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
fn validate_allocation_id(allocation_id: &str) -> Result<(), ExecutionWorkspaceDiagnostic> {
|
||||
fn validate_allocation_id(allocation_id: &str) -> Result<(), WorkingDirectoryDiagnostic> {
|
||||
let sanitized = sanitize_path_component(allocation_id);
|
||||
if allocation_id.is_empty()
|
||||
|| allocation_id != sanitized
|
||||
@@ -620,9 +606,9 @@ fn validate_allocation_id(allocation_id: &str) -> Result<(), ExecutionWorkspaceD
|
||||
|| allocation_id.contains('/')
|
||||
|| allocation_id.contains('\\')
|
||||
{
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_allocation_id_invalid",
|
||||
"execution workspace allocation id is invalid",
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_allocation_id_invalid",
|
||||
"working directory allocation id is invalid",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
@@ -631,11 +617,11 @@ fn validate_allocation_id(allocation_id: &str) -> Result<(), ExecutionWorkspaceD
|
||||
fn validate_relative_cwd(
|
||||
workspace_root: &Path,
|
||||
relative_cwd: Option<&str>,
|
||||
) -> Result<PathBuf, ExecutionWorkspaceDiagnostic> {
|
||||
) -> Result<PathBuf, WorkingDirectoryDiagnostic> {
|
||||
let workspace_root = workspace_root.canonicalize().map_err(|_| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_root_unavailable",
|
||||
"execution workspace root is unavailable; backend-private path details were omitted",
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_root_unavailable",
|
||||
"working directory root is unavailable; backend-private path details were omitted",
|
||||
)
|
||||
})?;
|
||||
let relative = relative_cwd.unwrap_or(".").trim();
|
||||
@@ -651,24 +637,24 @@ fn validate_relative_cwd(
|
||||
)
|
||||
})
|
||||
{
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_relative_cwd_invalid",
|
||||
"execution workspace relative_cwd must be a relative path inside the allocation root",
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_relative_cwd_invalid",
|
||||
"working directory relative_cwd must be a relative path inside the allocation root",
|
||||
));
|
||||
}
|
||||
let target = workspace_root
|
||||
.join(relative_path)
|
||||
.canonicalize()
|
||||
.map_err(|_| {
|
||||
ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_relative_cwd_unavailable",
|
||||
"execution workspace relative_cwd does not identify an existing directory",
|
||||
WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_relative_cwd_unavailable",
|
||||
"working directory relative_cwd does not identify an existing directory",
|
||||
)
|
||||
})?;
|
||||
if !target.starts_with(&workspace_root) || !target.is_dir() {
|
||||
return Err(ExecutionWorkspaceDiagnostic::new(
|
||||
"execution_workspace_relative_cwd_escape_rejected",
|
||||
"execution workspace relative_cwd must resolve to a directory inside the allocation root",
|
||||
return Err(WorkingDirectoryDiagnostic::new(
|
||||
"working_directory_relative_cwd_escape_rejected",
|
||||
"working directory relative_cwd must resolve to a directory inside the allocation root",
|
||||
));
|
||||
}
|
||||
Ok(target)
|
||||
@@ -677,7 +663,7 @@ fn validate_relative_cwd(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::catalog::{ExecutionWorkspaceRepository, RepositorySelector};
|
||||
use crate::catalog::{RepositorySelector, WorkingDirectoryRepository};
|
||||
use crate::identity::{RuntimeId, WorkerId};
|
||||
|
||||
fn git(path: &Path, args: &[&str]) {
|
||||
@@ -704,9 +690,9 @@ mod tests {
|
||||
dir
|
||||
}
|
||||
|
||||
fn request(repo: &Path) -> ExecutionWorkspaceRequest {
|
||||
ExecutionWorkspaceRequest {
|
||||
repository: ExecutionWorkspaceRepository {
|
||||
fn request(repo: &Path) -> WorkingDirectoryRequest {
|
||||
WorkingDirectoryRequest {
|
||||
repository: WorkingDirectoryRepository {
|
||||
id: "repo-main".to_string(),
|
||||
provider: "git".to_string(),
|
||||
uri: ".".to_string(),
|
||||
@@ -787,7 +773,7 @@ mod tests {
|
||||
.materialize(&worker_ref(1), &request(repo.path()))
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(error.code, "execution_workspace_dirty_source_rejected");
|
||||
assert_eq!(error.code, "working_directory_dirty_source_rejected");
|
||||
assert!(error.message.contains("clean_point_only"));
|
||||
}
|
||||
|
||||
@@ -803,7 +789,7 @@ mod tests {
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
error.code,
|
||||
"execution_workspace_remote_repository_unsupported"
|
||||
"working_directory_remote_repository_unsupported"
|
||||
);
|
||||
|
||||
let mut non_git = remote;
|
||||
@@ -814,7 +800,7 @@ mod tests {
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
error.code,
|
||||
"execution_workspace_repository_provider_unsupported"
|
||||
"working_directory_repository_provider_unsupported"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -858,28 +844,28 @@ mod tests {
|
||||
.bind_allocation(&allocation.allocation.id, Some("/tmp"))
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"execution_workspace_relative_cwd_invalid"
|
||||
"working_directory_relative_cwd_invalid"
|
||||
);
|
||||
assert_eq!(
|
||||
materializer
|
||||
.bind_allocation(&allocation.allocation.id, Some("../outside"))
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"execution_workspace_relative_cwd_invalid"
|
||||
"working_directory_relative_cwd_invalid"
|
||||
);
|
||||
assert_eq!(
|
||||
materializer
|
||||
.bind_allocation(&allocation.allocation.id, Some("missing"))
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"execution_workspace_relative_cwd_unavailable"
|
||||
"working_directory_relative_cwd_unavailable"
|
||||
);
|
||||
assert_eq!(
|
||||
materializer
|
||||
.bind_allocation(&allocation.allocation.id, Some("inside/file.txt"))
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"execution_workspace_relative_cwd_escape_rejected"
|
||||
"working_directory_relative_cwd_escape_rejected"
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -890,7 +876,7 @@ mod tests {
|
||||
.bind_allocation(&allocation.allocation.id, Some("escape"))
|
||||
.unwrap_err()
|
||||
.code,
|
||||
"execution_workspace_relative_cwd_escape_rejected"
|
||||
"working_directory_relative_cwd_escape_rejected"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -387,9 +387,9 @@ fn spawn_companion_worker(runtime: &RuntimeRegistry) -> CompanionWorkerState {
|
||||
},
|
||||
profile: Some(selector),
|
||||
initial_input: None,
|
||||
execution_workspace: None,
|
||||
resolved_execution_workspace: None,
|
||||
resolved_execution_workspace_allocation: None,
|
||||
working_directory: None,
|
||||
resolved_working_directory: None,
|
||||
resolved_working_directory_allocation: None,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -578,8 +578,8 @@ mod tests {
|
||||
"deterministic-companion-test",
|
||||
),
|
||||
run_state: WorkerExecutionRunState::Idle,
|
||||
execution_workspace: request
|
||||
.execution_workspace
|
||||
working_directory: request
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.map(|binding| binding.status()),
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use worker_runtime::catalog::{
|
||||
ConfigBundleRef, CreateWorkerRequest, ExecutionWorkspaceAllocationClaim,
|
||||
ExecutionWorkspaceRequest, ExecutionWorkspaceSummary, ProfileSelector,
|
||||
WorkerDetail as EmbeddedWorkerDetail, WorkerStatus as EmbeddedWorkerStatus,
|
||||
ConfigBundleRef, CreateWorkerRequest, ProfileSelector, WorkerDetail as EmbeddedWorkerDetail,
|
||||
WorkerStatus as EmbeddedWorkerStatus, WorkingDirectoryAllocationClaim, WorkingDirectoryRequest,
|
||||
WorkingDirectorySummary,
|
||||
};
|
||||
use worker_runtime::config_bundle::{
|
||||
ConfigBundle, ConfigBundleAvailability, ConfigBundleMetadata, ConfigBundleProvenance,
|
||||
@@ -240,7 +240,7 @@ pub struct WorkerSummary {
|
||||
pub implementation: WorkerImplementationSummary,
|
||||
pub capabilities: WorkerCapabilitySummary,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub execution_workspace: Option<ExecutionWorkspaceSummary>,
|
||||
pub working_directory: Option<WorkingDirectorySummary>,
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
}
|
||||
|
||||
@@ -268,14 +268,14 @@ pub struct WorkerLookupResult {
|
||||
/// The request carries Browser-facing launch semantics only: workspace intent,
|
||||
/// optional display identity, acceptance policy, optional profile selector,
|
||||
/// optional initial input, and optional configured Repository selector for
|
||||
/// execution-workspace materialization. Runtime execution authority is resolved by the host
|
||||
/// working-directory materialization. Runtime execution authority is resolved by the host
|
||||
/// into a synced ConfigBundle before the canonical Runtime create request is
|
||||
/// built. Raw workspace roots, child cwd, executable paths, tool scope,
|
||||
/// credentials, raw config stores, sockets, sessions, and storage paths are not
|
||||
/// accepted from Workspace API callers.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WorkerSpawnExecutionWorkspaceRequest {
|
||||
pub struct WorkerSpawnWorkingDirectoryRequest {
|
||||
/// Safe configured Repository id. The host resolves this id to repository
|
||||
/// authority from server-side config; browser callers cannot provide raw
|
||||
/// source paths or runtime-internal storage paths.
|
||||
@@ -295,15 +295,15 @@ pub struct WorkerSpawnRequest {
|
||||
pub profile: Option<ProfileSelector>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub initial_input: Option<EmbeddedWorkerInput>,
|
||||
/// Optional safe execution-workspace selector. The Workspace server resolves
|
||||
/// this into a runtime-internal `ExecutionWorkspaceRequest` from configured
|
||||
/// Optional safe working-directory selector. The Workspace server resolves
|
||||
/// this into a runtime-internal `WorkingDirectoryRequest` from configured
|
||||
/// repositories before calling a host.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub execution_workspace: Option<WorkerSpawnExecutionWorkspaceRequest>,
|
||||
pub working_directory: Option<WorkerSpawnWorkingDirectoryRequest>,
|
||||
#[serde(skip, default)]
|
||||
pub resolved_execution_workspace: Option<ExecutionWorkspaceRequest>,
|
||||
pub resolved_working_directory: Option<WorkingDirectoryRequest>,
|
||||
#[serde(skip, default)]
|
||||
pub resolved_execution_workspace_allocation: Option<ExecutionWorkspaceAllocationClaim>,
|
||||
pub resolved_working_directory_allocation: Option<WorkingDirectoryAllocationClaim>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
@@ -1143,9 +1143,9 @@ impl EmbeddedWorkerRuntime {
|
||||
can_stop: self.can_stop_embedded_worker(summary.status, &summary.execution),
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
execution_workspace: summary
|
||||
working_directory: summary
|
||||
.execution
|
||||
.execution_workspace
|
||||
.working_directory
|
||||
.clone()
|
||||
.map(|status| status.summary),
|
||||
diagnostics: embedded_worker_projection_diagnostics(&summary.execution),
|
||||
@@ -1177,9 +1177,9 @@ impl EmbeddedWorkerRuntime {
|
||||
can_stop: self.can_stop_embedded_worker(detail.status, &detail.execution),
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
execution_workspace: detail
|
||||
working_directory: detail
|
||||
.execution
|
||||
.execution_workspace
|
||||
.working_directory
|
||||
.clone()
|
||||
.map(|status| status.summary),
|
||||
diagnostics: embedded_worker_projection_diagnostics(&detail.execution),
|
||||
@@ -1356,8 +1356,8 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime {
|
||||
profile,
|
||||
config_bundle,
|
||||
initial_input: request.initial_input.clone(),
|
||||
execution_workspace: request.resolved_execution_workspace.clone(),
|
||||
execution_workspace_allocation: request.resolved_execution_workspace_allocation.clone(),
|
||||
working_directory: request.resolved_working_directory.clone(),
|
||||
working_directory_allocation: request.resolved_working_directory_allocation.clone(),
|
||||
};
|
||||
match self.runtime.create_worker(create_request) {
|
||||
Ok(detail) => {
|
||||
@@ -1833,7 +1833,7 @@ impl RemoteWorkerRuntime {
|
||||
can_stop: runtime_worker_can_stop(true, summary.status, &summary.execution),
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
execution_workspace: summary.execution.execution_workspace.clone().map(|status| status.summary),
|
||||
working_directory: summary.execution.working_directory.clone().map(|status| status.summary),
|
||||
diagnostics: vec![diagnostic(
|
||||
"remote_runtime_projection",
|
||||
DiagnosticSeverity::Info,
|
||||
@@ -1867,7 +1867,7 @@ impl RemoteWorkerRuntime {
|
||||
can_stop: runtime_worker_can_stop(true, detail.status, &detail.execution),
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
execution_workspace: detail.execution.execution_workspace.clone().map(|status| status.summary),
|
||||
working_directory: detail.execution.working_directory.clone().map(|status| status.summary),
|
||||
diagnostics: vec![diagnostic(
|
||||
"remote_runtime_projection",
|
||||
DiagnosticSeverity::Info,
|
||||
@@ -2032,8 +2032,8 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime {
|
||||
profile,
|
||||
config_bundle,
|
||||
initial_input: request.initial_input.clone(),
|
||||
execution_workspace: request.resolved_execution_workspace.clone(),
|
||||
execution_workspace_allocation: request.resolved_execution_workspace_allocation.clone(),
|
||||
working_directory: request.resolved_working_directory.clone(),
|
||||
working_directory_allocation: request.resolved_working_directory_allocation.clone(),
|
||||
};
|
||||
match self.post_json::<_, RuntimeHttpWorkerResponse>("/v1/workers", &create) {
|
||||
Ok(response) => WorkerSpawnResult {
|
||||
@@ -2844,7 +2844,7 @@ pub fn placeholder_worker(host_id: impl Into<String>) -> WorkerSummary {
|
||||
can_stop: false,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
execution_workspace: None,
|
||||
working_directory: None,
|
||||
diagnostics: vec![diagnostic(
|
||||
"runtime_capability_unsupported",
|
||||
DiagnosticSeverity::Info,
|
||||
@@ -2958,8 +2958,8 @@ mod tests {
|
||||
self.backend_id(),
|
||||
),
|
||||
run_state: WorkerExecutionRunState::Idle,
|
||||
execution_workspace: request
|
||||
.execution_workspace
|
||||
working_directory: request
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.map(|binding| binding.status()),
|
||||
}
|
||||
@@ -3040,7 +3040,7 @@ mod tests {
|
||||
can_stop: false,
|
||||
can_spawn_followup: false,
|
||||
},
|
||||
execution_workspace: None,
|
||||
working_directory: None,
|
||||
diagnostics: Vec::new(),
|
||||
}],
|
||||
}
|
||||
@@ -3191,9 +3191,9 @@ mod tests {
|
||||
},
|
||||
profile: None,
|
||||
initial_input: None,
|
||||
execution_workspace: None,
|
||||
resolved_execution_workspace: None,
|
||||
resolved_execution_workspace_allocation: None,
|
||||
working_directory: None,
|
||||
resolved_working_directory: None,
|
||||
resolved_working_directory_allocation: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3323,9 +3323,9 @@ mod tests {
|
||||
},
|
||||
profile: None,
|
||||
initial_input: None,
|
||||
execution_workspace: None,
|
||||
resolved_execution_workspace: None,
|
||||
resolved_execution_workspace_allocation: None,
|
||||
working_directory: None,
|
||||
resolved_working_directory: None,
|
||||
resolved_working_directory_allocation: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
@@ -3425,9 +3425,9 @@ mod tests {
|
||||
},
|
||||
profile: Some(ProfileSelector::Builtin("builtin:coder".to_string())),
|
||||
initial_input: None,
|
||||
execution_workspace: None,
|
||||
resolved_execution_workspace: None,
|
||||
resolved_execution_workspace_allocation: None,
|
||||
working_directory: None,
|
||||
resolved_working_directory: None,
|
||||
resolved_working_directory_allocation: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
@@ -3456,9 +3456,9 @@ mod tests {
|
||||
acceptance: WorkerSpawnAcceptanceRequirement::SocketReady,
|
||||
profile: None,
|
||||
initial_input: None,
|
||||
execution_workspace: None,
|
||||
resolved_execution_workspace: None,
|
||||
resolved_execution_workspace_allocation: None,
|
||||
working_directory: None,
|
||||
resolved_working_directory: None,
|
||||
resolved_working_directory_allocation: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -12,10 +12,10 @@ use chrono::{SecondsFormat, Utc};
|
||||
use futures::StreamExt;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::net::TcpListener;
|
||||
use worker_runtime::execution_workspace::{
|
||||
ExecutionWorkspaceDiagnostic, ExecutionWorkspaceMaterializer, LocalGitWorktreeMaterializer,
|
||||
};
|
||||
use worker_runtime::worker_backend::WorkerRuntimeExecutionBackend;
|
||||
use worker_runtime::working_directory::{
|
||||
LocalGitWorktreeMaterializer, WorkingDirectoryDiagnostic, WorkingDirectoryMaterializer,
|
||||
};
|
||||
|
||||
use crate::companion::{
|
||||
CompanionCancelRequest, CompanionConsole, CompanionMessageRequest, CompanionMessageResponse,
|
||||
@@ -27,8 +27,8 @@ use crate::hosts::{
|
||||
HostSummary, RemoteRuntimeConfig, RemoteWorkerRuntime, RuntimeDiagnostic, RuntimeRegistry,
|
||||
RuntimeRegistryUnregisterResult, RuntimeSummary, WorkerInputRequest, WorkerInputResult,
|
||||
WorkerLifecycleRequest, WorkerLifecycleResult, WorkerOperationState,
|
||||
WorkerSpawnAcceptanceRequirement, WorkerSpawnExecutionWorkspaceRequest, WorkerSpawnIntent,
|
||||
WorkerSpawnRequest, WorkerSpawnResult, WorkerSummary, WorkerTranscriptProjection,
|
||||
WorkerSpawnAcceptanceRequirement, WorkerSpawnIntent, WorkerSpawnRequest, WorkerSpawnResult,
|
||||
WorkerSpawnWorkingDirectoryRequest, WorkerSummary, WorkerTranscriptProjection,
|
||||
};
|
||||
use crate::identity::WorkspaceIdentity;
|
||||
use crate::observation::{
|
||||
@@ -45,9 +45,9 @@ use crate::repositories::{
|
||||
use crate::store::{ControlPlaneStore, WorkspaceRecord};
|
||||
use crate::{Error, Result};
|
||||
use worker_runtime::catalog::{
|
||||
ConfigBundleRef, DirtyStatePolicy, ExecutionWorkspaceAllocationClaim,
|
||||
ExecutionWorkspaceRepository, ExecutionWorkspaceRequest, ExecutionWorkspaceSummary,
|
||||
MaterializerKind, ProfileSelector, RepositorySelector as RuntimeRepositorySelector,
|
||||
ConfigBundleRef, DirtyStatePolicy, MaterializerKind, ProfileSelector,
|
||||
RepositorySelector as RuntimeRepositorySelector, WorkingDirectoryAllocationClaim,
|
||||
WorkingDirectoryRepository, WorkingDirectoryRequest, WorkingDirectorySummary,
|
||||
};
|
||||
use worker_runtime::config_bundle::ConfigBundle;
|
||||
use worker_runtime::http_server::{
|
||||
@@ -154,7 +154,7 @@ pub struct WorkspaceApi {
|
||||
runtime: Arc<RuntimeRegistry>,
|
||||
companion: Arc<CompanionConsole>,
|
||||
observation_proxy: BackendObservationProxy,
|
||||
execution_workspace_materializer: Arc<LocalGitWorktreeMaterializer>,
|
||||
working_directory_materializer: Arc<LocalGitWorktreeMaterializer>,
|
||||
}
|
||||
|
||||
impl WorkspaceApi {
|
||||
@@ -169,7 +169,7 @@ impl WorkspaceApi {
|
||||
"failed to initialize embedded Worker backend: {err}"
|
||||
))
|
||||
})?
|
||||
.with_execution_workspace_materializer((*materializer).clone());
|
||||
.with_working_directory_materializer((*materializer).clone());
|
||||
Self::new_with_execution_backend(config, store, Arc::new(execution_backend)).await
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ impl WorkspaceApi {
|
||||
let runtime = Arc::new(runtime);
|
||||
let companion = Arc::new(CompanionConsole::new(runtime.clone()));
|
||||
let observation_proxy = BackendObservationProxy::new(config.runtime_event_sources.clone());
|
||||
let execution_workspace_materializer = Arc::new(LocalGitWorktreeMaterializer::new(
|
||||
let working_directory_materializer = Arc::new(LocalGitWorktreeMaterializer::new(
|
||||
config.embedded_runtime_store_root.clone(),
|
||||
));
|
||||
Ok(Self {
|
||||
@@ -214,7 +214,7 @@ impl WorkspaceApi {
|
||||
runtime,
|
||||
companion,
|
||||
observation_proxy,
|
||||
execution_workspace_materializer,
|
||||
working_directory_materializer,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -271,12 +271,12 @@ pub fn build_router(api: WorkspaceApi) -> Router {
|
||||
.route("/api/hosts", get(list_hosts))
|
||||
.route("/api/w/{workspace_id}/hosts", get(scoped_list_hosts))
|
||||
.route(
|
||||
"/api/w/{workspace_id}/execution-workspaces",
|
||||
get(scoped_list_execution_workspaces).post(scoped_create_execution_workspace),
|
||||
"/api/w/{workspace_id}/working-directories",
|
||||
get(scoped_list_working_directories).post(scoped_create_working_directory),
|
||||
)
|
||||
.route(
|
||||
"/api/w/{workspace_id}/execution-workspaces/{allocation_id}",
|
||||
get(scoped_execution_workspace_detail).delete(scoped_cleanup_execution_workspace),
|
||||
"/api/w/{workspace_id}/working-directories/{allocation_id}",
|
||||
get(scoped_working_directory_detail).delete(scoped_cleanup_working_directory),
|
||||
)
|
||||
.route("/api/runtimes", get(list_runtimes))
|
||||
.route("/api/w/{workspace_id}/runtimes", get(scoped_list_runtimes))
|
||||
@@ -547,8 +547,8 @@ pub struct WorkerLaunchOptionsResponse {
|
||||
pub workspace_id: String,
|
||||
pub runtimes: Vec<WorkerLaunchRuntimeOption>,
|
||||
pub profiles: Vec<WorkerLaunchProfileCandidate>,
|
||||
pub repositories: Vec<ExecutionWorkspaceRepositoryOption>,
|
||||
pub execution_workspaces: Vec<ExecutionWorkspaceSummary>,
|
||||
pub repositories: Vec<WorkingDirectoryRepositoryOption>,
|
||||
pub working_directories: Vec<WorkingDirectorySummary>,
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
}
|
||||
|
||||
@@ -570,7 +570,7 @@ pub struct WorkerLaunchProfileCandidate {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ExecutionWorkspaceRepositoryOption {
|
||||
pub struct WorkingDirectoryRepositoryOption {
|
||||
pub id: String,
|
||||
pub display_name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -578,54 +578,54 @@ pub struct ExecutionWorkspaceRepositoryOption {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct BrowserExecutionWorkspaceCreatePolicy {
|
||||
pub struct BrowserWorkingDirectoryCreatePolicy {
|
||||
#[serde(default)]
|
||||
pub dirty_state: BrowserExecutionWorkspaceDirtyStatePolicy,
|
||||
pub dirty_state: BrowserWorkingDirectoryDirtyStatePolicy,
|
||||
#[serde(default)]
|
||||
pub cleanup: BrowserExecutionWorkspaceCleanupPolicy,
|
||||
pub cleanup: BrowserWorkingDirectoryCleanupPolicy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BrowserExecutionWorkspaceDirtyStatePolicy {
|
||||
pub enum BrowserWorkingDirectoryDirtyStatePolicy {
|
||||
#[default]
|
||||
CleanPointOnly,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BrowserExecutionWorkspaceCleanupPolicy {
|
||||
pub enum BrowserWorkingDirectoryCleanupPolicy {
|
||||
#[default]
|
||||
ManualOrWorkerStop,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct BrowserExecutionWorkspaceCreateRequest {
|
||||
pub struct BrowserWorkingDirectoryCreateRequest {
|
||||
pub repository_id: String,
|
||||
#[serde(default)]
|
||||
pub selector: Option<String>,
|
||||
#[serde(default)]
|
||||
pub policy: BrowserExecutionWorkspaceCreatePolicy,
|
||||
pub policy: BrowserWorkingDirectoryCreatePolicy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BrowserExecutionWorkspaceListResponse {
|
||||
pub struct BrowserWorkingDirectoryListResponse {
|
||||
pub workspace_id: String,
|
||||
pub items: Vec<ExecutionWorkspaceSummary>,
|
||||
pub items: Vec<WorkingDirectorySummary>,
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct BrowserExecutionWorkspaceDetailResponse {
|
||||
pub struct BrowserWorkingDirectoryDetailResponse {
|
||||
pub workspace_id: String,
|
||||
pub item: ExecutionWorkspaceSummary,
|
||||
pub item: WorkingDirectorySummary,
|
||||
pub diagnostics: Vec<RuntimeDiagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct BrowserWorkerExecutionWorkspaceSelection {
|
||||
pub struct BrowserWorkerWorkingDirectorySelection {
|
||||
pub allocation_id: String,
|
||||
#[serde(default)]
|
||||
pub relative_cwd: Option<String>,
|
||||
@@ -639,7 +639,7 @@ pub struct BrowserCreateWorkerRequest {
|
||||
pub profile: String,
|
||||
pub initial_text: String,
|
||||
#[serde(default)]
|
||||
pub execution_workspace: Option<BrowserWorkerExecutionWorkspaceSelection>,
|
||||
pub working_directory: Option<BrowserWorkerWorkingDirectorySelection>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -742,7 +742,7 @@ struct ScopedRuntimePath {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ScopedExecutionWorkspacePath {
|
||||
struct ScopedWorkingDirectoryPath {
|
||||
workspace_id: String,
|
||||
allocation_id: String,
|
||||
}
|
||||
@@ -884,28 +884,28 @@ async fn scoped_get_worker_launch_options(
|
||||
get_worker_launch_options(State(api)).await
|
||||
}
|
||||
|
||||
async fn scoped_list_execution_workspaces(
|
||||
async fn scoped_list_working_directories(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
||||
) -> ApiResult<Json<BrowserExecutionWorkspaceListResponse>> {
|
||||
) -> ApiResult<Json<BrowserWorkingDirectoryListResponse>> {
|
||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||
let items = execution_workspace_summaries(&api)?;
|
||||
Ok(Json(BrowserExecutionWorkspaceListResponse {
|
||||
let items = working_directory_summaries(&api)?;
|
||||
Ok(Json(BrowserWorkingDirectoryListResponse {
|
||||
workspace_id: api.config.workspace_id.clone(),
|
||||
items,
|
||||
diagnostics: Vec::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn scoped_create_execution_workspace(
|
||||
async fn scoped_create_working_directory(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath(path): AxumPath<ScopedWorkspacePath>,
|
||||
Json(request): Json<BrowserExecutionWorkspaceCreateRequest>,
|
||||
) -> ApiResult<Json<BrowserExecutionWorkspaceDetailResponse>> {
|
||||
Json(request): Json<BrowserWorkingDirectoryCreateRequest>,
|
||||
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||
let workspace_request = execution_workspace_request_for_browser(&api, request)?;
|
||||
let workspace_request = working_directory_request_for_browser(&api, request)?;
|
||||
let binding = api
|
||||
.execution_workspace_materializer
|
||||
.working_directory_materializer
|
||||
.preallocate(&workspace_request)
|
||||
.map_err(|diagnostic| {
|
||||
ApiError::from(Error::RuntimeOperationFailed {
|
||||
@@ -914,20 +914,20 @@ async fn scoped_create_execution_workspace(
|
||||
message: diagnostic.message,
|
||||
})
|
||||
})?;
|
||||
Ok(Json(BrowserExecutionWorkspaceDetailResponse {
|
||||
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||
workspace_id: api.config.workspace_id.clone(),
|
||||
item: binding.status().summary,
|
||||
diagnostics: Vec::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn scoped_execution_workspace_detail(
|
||||
async fn scoped_working_directory_detail(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath(path): AxumPath<ScopedExecutionWorkspacePath>,
|
||||
) -> ApiResult<Json<BrowserExecutionWorkspaceDetailResponse>> {
|
||||
AxumPath(path): AxumPath<ScopedWorkingDirectoryPath>,
|
||||
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||
let status = api
|
||||
.execution_workspace_materializer
|
||||
.working_directory_materializer
|
||||
.allocation_status(&path.allocation_id)
|
||||
.map_err(|diagnostic| {
|
||||
ApiError::from(Error::RuntimeOperationFailed {
|
||||
@@ -936,20 +936,20 @@ async fn scoped_execution_workspace_detail(
|
||||
message: diagnostic.message,
|
||||
})
|
||||
})?;
|
||||
Ok(Json(BrowserExecutionWorkspaceDetailResponse {
|
||||
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||
workspace_id: api.config.workspace_id.clone(),
|
||||
item: status.summary,
|
||||
diagnostics: Vec::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn scoped_cleanup_execution_workspace(
|
||||
async fn scoped_cleanup_working_directory(
|
||||
State(api): State<WorkspaceApi>,
|
||||
AxumPath(path): AxumPath<ScopedExecutionWorkspacePath>,
|
||||
) -> ApiResult<Json<BrowserExecutionWorkspaceDetailResponse>> {
|
||||
AxumPath(path): AxumPath<ScopedWorkingDirectoryPath>,
|
||||
) -> ApiResult<Json<BrowserWorkingDirectoryDetailResponse>> {
|
||||
validate_workspace_scope(&api, &path.workspace_id)?;
|
||||
let status = api
|
||||
.execution_workspace_materializer
|
||||
.working_directory_materializer
|
||||
.cleanup_allocation(&path.allocation_id)
|
||||
.map_err(|diagnostic| {
|
||||
ApiError::from(Error::RuntimeOperationFailed {
|
||||
@@ -958,7 +958,7 @@ async fn scoped_cleanup_execution_workspace(
|
||||
message: diagnostic.message,
|
||||
})
|
||||
})?;
|
||||
Ok(Json(BrowserExecutionWorkspaceDetailResponse {
|
||||
Ok(Json(BrowserWorkingDirectoryDetailResponse {
|
||||
workspace_id: api.config.workspace_id.clone(),
|
||||
item: status.summary,
|
||||
diagnostics: Vec::new(),
|
||||
@@ -1551,12 +1551,12 @@ async fn get_worker_launch_options(
|
||||
Ok(Json(worker_launch_options_response(&api)))
|
||||
}
|
||||
|
||||
fn execution_workspace_request_from_repository(
|
||||
fn working_directory_request_from_repository(
|
||||
repository: &ConfiguredRepository,
|
||||
selector: Option<&str>,
|
||||
) -> ExecutionWorkspaceRequest {
|
||||
ExecutionWorkspaceRequest {
|
||||
repository: ExecutionWorkspaceRepository {
|
||||
) -> WorkingDirectoryRequest {
|
||||
WorkingDirectoryRequest {
|
||||
repository: WorkingDirectoryRepository {
|
||||
id: repository.id.clone(),
|
||||
provider: repository.provider.clone(),
|
||||
uri: repository.uri.clone(),
|
||||
@@ -1576,21 +1576,21 @@ fn execution_workspace_request_from_repository(
|
||||
}
|
||||
}
|
||||
|
||||
fn configured_execution_workspace_request(
|
||||
fn configured_working_directory_request(
|
||||
config: &ServerConfig,
|
||||
request: &WorkerSpawnExecutionWorkspaceRequest,
|
||||
) -> Result<ExecutionWorkspaceRequest> {
|
||||
request: &WorkerSpawnWorkingDirectoryRequest,
|
||||
) -> Result<WorkingDirectoryRequest> {
|
||||
let repository = config
|
||||
.repositories
|
||||
.iter()
|
||||
.find(|repository| repository.id == request.repository_id)
|
||||
.ok_or_else(|| {
|
||||
Error::Config(format!(
|
||||
"unknown repository id `{}` for Worker execution workspace",
|
||||
"unknown repository id `{}` for Worker working directory",
|
||||
request.repository_id
|
||||
))
|
||||
})?;
|
||||
Ok(execution_workspace_request_from_repository(
|
||||
Ok(working_directory_request_from_repository(
|
||||
repository,
|
||||
request.selector.as_deref(),
|
||||
))
|
||||
@@ -1621,31 +1621,31 @@ async fn create_workspace_worker(
|
||||
content: initial_text,
|
||||
})
|
||||
};
|
||||
let resolved_execution_workspace_allocation =
|
||||
let resolved_working_directory_allocation =
|
||||
request
|
||||
.execution_workspace
|
||||
.map(|selection| ExecutionWorkspaceAllocationClaim {
|
||||
.working_directory
|
||||
.map(|selection| WorkingDirectoryAllocationClaim {
|
||||
allocation_id: selection.allocation_id,
|
||||
relative_cwd: selection.relative_cwd,
|
||||
});
|
||||
if resolved_execution_workspace_allocation.is_some()
|
||||
if resolved_working_directory_allocation.is_some()
|
||||
&& request.runtime_id != EMBEDDED_WORKER_RUNTIME_ID
|
||||
{
|
||||
return Err(Error::RuntimeOperationFailed {
|
||||
runtime_id: request.runtime_id.clone(),
|
||||
code: "execution_workspace_runtime_unsupported".to_string(),
|
||||
message: "preallocated execution workspaces are only supported by the embedded local runtime in v0".to_string(),
|
||||
code: "working_directory_runtime_unsupported".to_string(),
|
||||
message: "preallocated working directories are only supported by the embedded local runtime in v0".to_string(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
if let Some(allocation) = resolved_execution_workspace_allocation.as_ref() {
|
||||
api.execution_workspace_materializer
|
||||
if let Some(allocation) = resolved_working_directory_allocation.as_ref() {
|
||||
api.working_directory_materializer
|
||||
.bind_allocation(
|
||||
&allocation.allocation_id,
|
||||
allocation.relative_cwd.as_deref(),
|
||||
)
|
||||
.map_err(|diagnostic| {
|
||||
execution_workspace_api_error(request.runtime_id.clone(), diagnostic)
|
||||
working_directory_api_error(request.runtime_id.clone(), diagnostic)
|
||||
})?;
|
||||
}
|
||||
let result = api
|
||||
@@ -1660,9 +1660,9 @@ async fn create_workspace_worker(
|
||||
},
|
||||
profile: Some(profile_selector),
|
||||
initial_input,
|
||||
execution_workspace: None,
|
||||
resolved_execution_workspace: None,
|
||||
resolved_execution_workspace_allocation,
|
||||
working_directory: None,
|
||||
resolved_working_directory: None,
|
||||
resolved_working_directory_allocation,
|
||||
},
|
||||
)
|
||||
.map_err(|err| err.into_error())?;
|
||||
@@ -1751,11 +1751,11 @@ async fn create_runtime_worker(
|
||||
AxumPath(runtime_id): AxumPath<String>,
|
||||
Json(mut request): Json<WorkerSpawnRequest>,
|
||||
) -> ApiResult<Json<WorkerSpawnResult>> {
|
||||
request.resolved_execution_workspace = request
|
||||
.execution_workspace
|
||||
request.resolved_working_directory = request
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.map(|execution_workspace| {
|
||||
configured_execution_workspace_request(&api.config, execution_workspace)
|
||||
.map(|working_directory| {
|
||||
configured_working_directory_request(&api.config, working_directory)
|
||||
})
|
||||
.transpose()?;
|
||||
let result = api
|
||||
@@ -2688,19 +2688,19 @@ fn worker_launch_options_response(api: &WorkspaceApi) -> WorkerLaunchOptionsResp
|
||||
workspace_id: api.config.workspace_id.clone(),
|
||||
runtimes,
|
||||
profiles: worker_profile_candidates(),
|
||||
repositories: execution_workspace_repository_options(api),
|
||||
execution_workspaces: execution_workspace_summaries(api).unwrap_or_default(),
|
||||
repositories: working_directory_repository_options(api),
|
||||
working_directories: working_directory_summaries(api).unwrap_or_default(),
|
||||
diagnostics: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn execution_workspace_repository_options(
|
||||
fn working_directory_repository_options(
|
||||
api: &WorkspaceApi,
|
||||
) -> Vec<ExecutionWorkspaceRepositoryOption> {
|
||||
) -> Vec<WorkingDirectoryRepositoryOption> {
|
||||
api.config
|
||||
.repositories
|
||||
.iter()
|
||||
.map(|repository| ExecutionWorkspaceRepositoryOption {
|
||||
.map(|repository| WorkingDirectoryRepositoryOption {
|
||||
id: repository.id.clone(),
|
||||
display_name: repository
|
||||
.display_name
|
||||
@@ -2711,8 +2711,8 @@ fn execution_workspace_repository_options(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn execution_workspace_summaries(api: &WorkspaceApi) -> ApiResult<Vec<ExecutionWorkspaceSummary>> {
|
||||
api.execution_workspace_materializer
|
||||
fn working_directory_summaries(api: &WorkspaceApi) -> ApiResult<Vec<WorkingDirectorySummary>> {
|
||||
api.working_directory_materializer
|
||||
.list_allocations()
|
||||
.map(|items| items.into_iter().map(|status| status.summary).collect())
|
||||
.map_err(|diagnostic| {
|
||||
@@ -2724,10 +2724,10 @@ fn execution_workspace_summaries(api: &WorkspaceApi) -> ApiResult<Vec<ExecutionW
|
||||
})
|
||||
}
|
||||
|
||||
fn execution_workspace_request_for_browser(
|
||||
fn working_directory_request_for_browser(
|
||||
api: &WorkspaceApi,
|
||||
request: BrowserExecutionWorkspaceCreateRequest,
|
||||
) -> ApiResult<ExecutionWorkspaceRequest> {
|
||||
request: BrowserWorkingDirectoryCreateRequest,
|
||||
) -> ApiResult<WorkingDirectoryRequest> {
|
||||
let repository = api
|
||||
.config
|
||||
.repositories
|
||||
@@ -2739,13 +2739,13 @@ fn execution_workspace_request_for_browser(
|
||||
.or_else(|| repository.default_selector.clone())
|
||||
.filter(|selector| !selector.trim().is_empty());
|
||||
match request.policy.dirty_state {
|
||||
BrowserExecutionWorkspaceDirtyStatePolicy::CleanPointOnly => {}
|
||||
BrowserWorkingDirectoryDirtyStatePolicy::CleanPointOnly => {}
|
||||
}
|
||||
match request.policy.cleanup {
|
||||
BrowserExecutionWorkspaceCleanupPolicy::ManualOrWorkerStop => {}
|
||||
BrowserWorkingDirectoryCleanupPolicy::ManualOrWorkerStop => {}
|
||||
}
|
||||
Ok(ExecutionWorkspaceRequest {
|
||||
repository: ExecutionWorkspaceRepository {
|
||||
Ok(WorkingDirectoryRequest {
|
||||
repository: WorkingDirectoryRepository {
|
||||
id: repository.id.clone(),
|
||||
provider: "git".to_string(),
|
||||
uri: repository.path.to_string_lossy().to_string(),
|
||||
@@ -2822,9 +2822,9 @@ fn worker_create_not_accepted_error(
|
||||
)
|
||||
}
|
||||
|
||||
fn execution_workspace_api_error(
|
||||
fn working_directory_api_error(
|
||||
runtime_id: String,
|
||||
diagnostic: ExecutionWorkspaceDiagnostic,
|
||||
diagnostic: WorkingDirectoryDiagnostic,
|
||||
) -> ApiError {
|
||||
ApiError::with_diagnostics(
|
||||
Error::RuntimeOperationFailed {
|
||||
@@ -3168,7 +3168,7 @@ impl IntoResponse for ApiError {
|
||||
if code.starts_with("workspace_settings_")
|
||||
|| code.starts_with("invalid_")
|
||||
|| code.starts_with("unsupported_worker_profile")
|
||||
|| code.starts_with("execution_workspace_")
|
||||
|| code.starts_with("working_directory_")
|
||||
|| code.ends_with("_already_exists")
|
||||
|| code.ends_with("_not_config_managed")
|
||||
|| code.ends_with("_unsupported") =>
|
||||
@@ -3284,8 +3284,8 @@ mod tests {
|
||||
self.backend_id(),
|
||||
),
|
||||
run_state: worker_runtime::execution::WorkerExecutionRunState::Idle,
|
||||
execution_workspace: request
|
||||
.execution_workspace
|
||||
working_directory: request
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.map(|binding| binding.status()),
|
||||
}
|
||||
@@ -3418,8 +3418,8 @@ mod tests {
|
||||
digest: bundle.metadata.digest,
|
||||
},
|
||||
initial_input: None,
|
||||
execution_workspace: None,
|
||||
execution_workspace_allocation: None,
|
||||
working_directory: None,
|
||||
working_directory_allocation: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3435,11 +3435,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn browser_execution_workspace_preallocate_list_detail_and_cleanup_are_path_safe() {
|
||||
async fn browser_working_directory_preallocate_list_detail_and_cleanup_are_path_safe() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
init_clean_git_workspace(dir.path());
|
||||
let app = test_app(dir.path()).await;
|
||||
let workspace_path = format!("/api/w/{TEST_WORKSPACE_ID}/execution-workspaces");
|
||||
let workspace_path = format!("/api/w/{TEST_WORKSPACE_ID}/working-directories");
|
||||
|
||||
let created = post_json(
|
||||
app.clone(),
|
||||
@@ -3478,10 +3478,10 @@ mod tests {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
init_clean_git_workspace(dir.path());
|
||||
let app = test_app(dir.path()).await;
|
||||
let workspaces_path = format!("/api/w/{TEST_WORKSPACE_ID}/execution-workspaces");
|
||||
let working_directories_path = format!("/api/w/{TEST_WORKSPACE_ID}/working-directories");
|
||||
let created = post_json(
|
||||
app.clone(),
|
||||
&workspaces_path,
|
||||
&working_directories_path,
|
||||
serde_json::json!({
|
||||
"repository_id": TEST_REPOSITORY_ID,
|
||||
"selector": "HEAD",
|
||||
@@ -3500,7 +3500,7 @@ mod tests {
|
||||
"display_name": "Coding Worker",
|
||||
"profile": "builtin:coder",
|
||||
"initial_text": "",
|
||||
"execution_workspace": {
|
||||
"working_directory": {
|
||||
"allocation_id": allocation_id,
|
||||
"relative_cwd": "../escape"
|
||||
}
|
||||
@@ -3513,14 +3513,14 @@ mod tests {
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|diagnostic| diagnostic["code"] == "execution_workspace_relative_cwd_invalid"),
|
||||
.any(|diagnostic| diagnostic["code"] == "working_directory_relative_cwd_invalid"),
|
||||
"expected typed relative_cwd diagnostic, got {response}"
|
||||
);
|
||||
assert!(
|
||||
response["message"]
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.contains("execution_workspace_relative_cwd_invalid")
|
||||
.contains("working_directory_relative_cwd_invalid")
|
||||
);
|
||||
let projected = serde_json::to_string(&response).unwrap();
|
||||
assert!(!projected.contains(dir.path().to_string_lossy().as_ref()));
|
||||
@@ -3840,7 +3840,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn runtime_worker_spawn_rejects_raw_execution_workspace_fields() {
|
||||
async fn runtime_worker_spawn_rejects_raw_working_directory_fields() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let app = test_app(dir.path()).await;
|
||||
let response = request_json(
|
||||
@@ -3857,7 +3857,7 @@ mod tests {
|
||||
"kind": "run_accepted",
|
||||
"expected_segments": 0
|
||||
},
|
||||
"execution_workspace": {
|
||||
"working_directory": {
|
||||
"repository_id": TEST_REPOSITORY_ID,
|
||||
"local_path": dir.path().display().to_string()
|
||||
}
|
||||
@@ -3870,7 +3870,7 @@ mod tests {
|
||||
.as_str()
|
||||
.unwrap_or_default()
|
||||
.contains("unknown field"),
|
||||
"raw execution workspace field should be rejected: {response}"
|
||||
"raw working directory field should be rejected: {response}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3892,7 +3892,7 @@ mod tests {
|
||||
"kind": "run_accepted",
|
||||
"expected_segments": 0
|
||||
},
|
||||
"execution_workspace": {
|
||||
"working_directory": {
|
||||
"repository_id": TEST_REPOSITORY_ID,
|
||||
"selector": "HEAD"
|
||||
}
|
||||
@@ -4379,9 +4379,9 @@ mod tests {
|
||||
},
|
||||
profile: None,
|
||||
initial_input: None,
|
||||
execution_workspace: None,
|
||||
resolved_execution_workspace: None,
|
||||
resolved_execution_workspace_allocation: None,
|
||||
working_directory: None,
|
||||
resolved_working_directory: None,
|
||||
resolved_working_directory_allocation: None,
|
||||
},
|
||||
)
|
||||
.expect("spawn worker");
|
||||
|
||||
Reference in New Issue
Block a user