diff --git a/crates/worker-runtime/src/catalog.rs b/crates/worker-runtime/src/catalog.rs index ec4eeace..1026eaa1 100644 --- a/crates/worker-runtime/src/catalog.rs +++ b/crates/worker-runtime/src/catalog.rs @@ -90,8 +90,8 @@ pub struct WorkingDirectoryRequest { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct WorkingDirectoryAllocationClaim { - pub allocation_id: String, +pub struct WorkingDirectoryClaim { + pub working_directory_id: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub relative_cwd: Option, } @@ -107,13 +107,13 @@ pub enum WorkingDirectoryStatusKind { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct WorkingDirectoryCleanupTarget { pub kind: String, - pub allocation_id: String, + pub working_directory_id: String, pub repository_id: String, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct WorkingDirectorySummary { - pub allocation_id: String, + pub working_directory_id: String, pub repository_id: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub requested_selector: Option, @@ -152,9 +152,9 @@ pub struct CreateWorkerRequest { #[serde(default, skip_serializing_if = "Option::is_none")] pub initial_input: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub working_directory: Option, + pub working_directory_request: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub working_directory_allocation: Option, + pub working_directory: Option, } /// Worker lifecycle status for the in-memory embedded runtime. diff --git a/crates/worker-runtime/src/http_server.rs b/crates/worker-runtime/src/http_server.rs index 16cef36d..e63fc7c3 100644 --- a/crates/worker-runtime/src/http_server.rs +++ b/crates/worker-runtime/src/http_server.rs @@ -863,8 +863,8 @@ mod tests { digest: bundle.metadata.digest, }, initial_input: None, + working_directory_request: None, working_directory: None, - working_directory_allocation: None, } } @@ -1148,8 +1148,8 @@ mod ws_tests { digest: bundle.metadata.digest, }, initial_input: None, + working_directory_request: None, working_directory: None, - working_directory_allocation: None, } } diff --git a/crates/worker-runtime/src/runtime.rs b/crates/worker-runtime/src/runtime.rs index 097567f1..44ef2309 100644 --- a/crates/worker-runtime/src/runtime.rs +++ b/crates/worker-runtime/src/runtime.rs @@ -1592,8 +1592,8 @@ mod tests { digest: bundle.metadata.digest, }, initial_input: None, + working_directory_request: None, working_directory: None, - working_directory_allocation: None, } } diff --git a/crates/worker-runtime/src/worker_backend.rs b/crates/worker-runtime/src/worker_backend.rs index 8480c1d4..71f94397 100644 --- a/crates/worker-runtime/src/worker_backend.rs +++ b/crates/worker-runtime/src/worker_backend.rs @@ -375,23 +375,23 @@ where let mut request = request; let working_directory = match ( + request.request.working_directory_request.as_ref(), 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 working_directory and working_directory_allocation", + "worker spawn cannot specify both working_directory_request and working_directory", )); } - (Some(workspace_request), None) => { + (Some(working_directory_request), None) => { let Some(materializer) = self.working_directory_materializer.as_ref() else { return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected( WorkerExecutionOperation::Spawn, "working directory materialization requested, but no materializer is configured for this runtime backend", )); }; - match materializer.materialize(&request.worker_ref, workspace_request) { + match materializer.materialize(&request.worker_ref, working_directory_request) { Ok(binding) => { request.working_directory = Some(binding.clone()); Some(binding) @@ -406,16 +406,16 @@ where } } } - (None, Some(allocation)) => { + (None, Some(working_directory)) => { let Some(materializer) = self.working_directory_materializer.as_ref() else { return WorkerExecutionSpawnResult::Rejected(WorkerExecutionResult::rejected( WorkerExecutionOperation::Spawn, - "working directory allocation requested, but no materializer is configured for this runtime backend", + "working directory working_directory requested, but no materializer is configured for this runtime backend", )); }; - match materializer.bind_allocation( - &allocation.allocation_id, - allocation.relative_cwd.as_deref(), + match materializer.bind_working_directory( + &working_directory.working_directory_id, + working_directory.relative_cwd.as_deref(), ) { Ok(binding) => { request.working_directory = Some(binding.clone()); @@ -780,8 +780,8 @@ mod tests { digest: bundle.metadata.digest, }, initial_input: None, + working_directory_request: None, working_directory: None, - working_directory_allocation: None, } } @@ -933,7 +933,7 @@ mod tests { .unwrap(); runtime.store_config_bundle(test_bundle()).unwrap(); let mut request = create_request("chat"); - request.working_directory = Some(working_directory_request(repo.path())); + request.working_directory_request = Some(working_directory_request(repo.path())); let detail = runtime.create_worker(request).unwrap(); diff --git a/crates/worker-runtime/src/working_directory.rs b/crates/worker-runtime/src/working_directory.rs index 8885823a..1053ba88 100644 --- a/crates/worker-runtime/src/working_directory.rs +++ b/crates/worker-runtime/src/working_directory.rs @@ -12,7 +12,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; const WORKING_DIRECTORIES_DIR: &str = "working-directories"; const MATERIALIZATION_RECORD: &str = "materialization.json"; -static NEXT_ALLOCATION_SEQUENCE: AtomicU64 = AtomicU64::new(0); +static NEXT_WORKING_DIRECTORY_SEQUENCE: AtomicU64 = AtomicU64::new(0); #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct WorkingDirectoryEvidence { @@ -27,7 +27,7 @@ pub struct WorkingDirectoryEvidence { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct WorkingDirectoryAllocation { +pub struct WorkingDirectory { pub id: String, pub repository_id: String, pub materializer_kind: MaterializerKind, @@ -38,10 +38,10 @@ pub struct WorkingDirectoryAllocation { pub status: WorkingDirectoryStatusKind, } -impl WorkingDirectoryAllocation { +impl WorkingDirectory { pub fn status_summary(&self) -> WorkingDirectorySummary { WorkingDirectorySummary { - allocation_id: self.id.clone(), + working_directory_id: self.id.clone(), repository_id: self.repository_id.clone(), requested_selector: self.evidence.requested_selector.clone(), materializer_kind: self.materializer_kind.clone(), @@ -57,10 +57,10 @@ impl WorkingDirectoryAllocation { #[derive(Clone, Debug, PartialEq, Eq)] pub struct WorkingDirectoryBinding { - pub allocation: WorkingDirectoryAllocation, + pub working_directory: WorkingDirectory, pub workspace_root: PathBuf, pub cwd: PathBuf, - allocation_root: PathBuf, + working_directory_root: PathBuf, source_repository_path: PathBuf, } @@ -73,8 +73,8 @@ impl WorkingDirectoryBinding { &self.cwd } - pub fn allocation_root(&self) -> &Path { - &self.allocation_root + pub fn working_directory_root(&self) -> &Path { + &self.working_directory_root } pub fn source_repository_path(&self) -> &Path { @@ -83,7 +83,7 @@ impl WorkingDirectoryBinding { pub fn status(&self) -> WorkingDirectoryStatus { WorkingDirectoryStatus { - summary: self.allocation.status_summary(), + summary: self.working_directory.status_summary(), } } } @@ -118,27 +118,29 @@ pub trait WorkingDirectoryMaterializer: Send + Sync + 'static { request: &WorkingDirectoryRequest, ) -> Result; - fn preallocate( + fn create( &self, request: &WorkingDirectoryRequest, ) -> Result; - fn bind_allocation( + fn bind_working_directory( &self, - allocation_id: &str, + working_directory_id: &str, relative_cwd: Option<&str>, ) -> Result; - fn list_allocations(&self) -> Result, WorkingDirectoryDiagnostic>; - - fn allocation_status( + fn list_working_directories( &self, - allocation_id: &str, + ) -> Result, WorkingDirectoryDiagnostic>; + + fn working_directory_status( + &self, + working_directory_id: &str, ) -> Result; - fn cleanup_allocation( + fn cleanup_working_directory( &self, - allocation_id: &str, + working_directory_id: &str, ) -> Result; fn cleanup(&self, binding: &WorkingDirectoryBinding) -> Result<(), WorkingDirectoryDiagnostic>; @@ -160,7 +162,7 @@ impl LocalGitWorktreeMaterializer { &self.runtime_root } - fn allocation_id(worker_ref: &WorkerRef, repository_id: &str) -> String { + fn working_directory_id(worker_ref: &WorkerRef, repository_id: &str) -> String { format!( "{}-{}-{}", sanitize_path_component(worker_ref.runtime_id.as_str()), @@ -169,10 +171,10 @@ impl LocalGitWorktreeMaterializer { ) } - fn allocation_root(&self, allocation_id: &str) -> PathBuf { + fn working_directory_root(&self, working_directory_id: &str) -> PathBuf { self.runtime_root .join(WORKING_DIRECTORIES_DIR) - .join(allocation_id) + .join(working_directory_id) } fn write_record( @@ -180,11 +182,11 @@ impl LocalGitWorktreeMaterializer { binding: &WorkingDirectoryBinding, ) -> Result<(), WorkingDirectoryDiagnostic> { let record = WorkingDirectoryMaterializationRecord { - allocation: binding.allocation.clone(), + working_directory: binding.working_directory.clone(), workspace_root: binding.workspace_root.clone(), source_repository_path: binding.source_repository_path.clone(), }; - let path = binding.allocation_root.join(MATERIALIZATION_RECORD); + let path = binding.working_directory_root.join(MATERIALIZATION_RECORD); let raw = serde_json::to_vec_pretty(&record).map_err(|error| { WorkingDirectoryDiagnostic::new( "working_directory_record_serialize_failed", @@ -201,38 +203,38 @@ impl LocalGitWorktreeMaterializer { fn read_binding( &self, - allocation_id: &str, + working_directory_id: &str, ) -> Result { - let allocation_root = self.allocation_root(allocation_id); - let path = allocation_root.join(MATERIALIZATION_RECORD); + let working_directory_root = self.working_directory_root(working_directory_id); + let path = working_directory_root.join(MATERIALIZATION_RECORD); let raw = fs::read(&path).map_err(|_| { WorkingDirectoryDiagnostic::new( - "working_directory_allocation_not_found", - "working directory allocation was not found", + "working_directory_not_found", + "working directory working_directory was not found", ) })?; 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", + "working directory working_directory record is invalid; backend-private path details were omitted", ) })?; Ok(WorkingDirectoryBinding { - allocation: record.allocation, + working_directory: record.working_directory, workspace_root: record.workspace_root.clone(), cwd: record.workspace_root, - allocation_root, + working_directory_root, source_repository_path: record.source_repository_path, }) } - fn materialize_with_allocation_id( + fn materialize_with_working_directory_id( &self, - allocation_id: String, + working_directory_id: String, request: &WorkingDirectoryRequest, cleanup_policy: &str, ) -> Result { - validate_allocation_id(&allocation_id)?; + validate_working_directory_id(&working_directory_id)?; if request.materializer != MaterializerKind::LocalGitWorktree { return Err(WorkingDirectoryDiagnostic::new( "working_directory_materializer_unsupported", @@ -299,26 +301,26 @@ impl LocalGitWorktreeMaterializer { .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()); - let allocation_root = self.allocation_root(&allocation_id); - let workspace_root = allocation_root + let working_directory_root = self.working_directory_root(&working_directory_id); + let workspace_root = working_directory_root .join("root") .join(sanitize_path_component(&request.repository.id)); if workspace_root.exists() { return Err(WorkingDirectoryDiagnostic::new( - "working_directory_allocation_exists", - "working directory allocation target already exists; cleanup or choose a new allocation", + "working_directory_exists", + "working directory working_directory target already exists; cleanup or choose a new working_directory", )); } fs::create_dir_all(workspace_root.parent().ok_or_else(|| { WorkingDirectoryDiagnostic::new( "working_directory_invalid_target", - "working directory allocation target has no parent directory", + "working directory working_directory target has no parent directory", ) })?) .map_err(|_| { WorkingDirectoryDiagnostic::new( "working_directory_create_failed", - "failed to create working directory allocation directory; backend-private path details were omitted", + "failed to create working directory working_directory directory; backend-private path details were omitted", ) })?; @@ -334,8 +336,8 @@ impl LocalGitWorktreeMaterializer { ], )?; - let allocation = WorkingDirectoryAllocation { - id: allocation_id.clone(), + let working_directory = WorkingDirectory { + id: working_directory_id.clone(), repository_id: request.repository.id.clone(), materializer_kind: MaterializerKind::LocalGitWorktree, dirty_state_policy: DirtyStatePolicy::CleanPointOnly, @@ -353,17 +355,17 @@ impl LocalGitWorktreeMaterializer { }, cleanup_target: WorkingDirectoryCleanupTarget { kind: "git_worktree".to_string(), - allocation_id, + working_directory_id, repository_id: request.repository.id.clone(), }, cleanup_policy: cleanup_policy.to_string(), status: WorkingDirectoryStatusKind::Active, }; let binding = WorkingDirectoryBinding { - allocation, + working_directory, workspace_root: workspace_root.clone(), cwd: workspace_root, - allocation_root, + working_directory_root, source_repository_path: source_root, }; self.write_record(&binding)?; @@ -377,36 +379,46 @@ impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer { worker_ref: &WorkerRef, request: &WorkingDirectoryRequest, ) -> Result { - let allocation_id = Self::allocation_id(worker_ref, &request.repository.id); - self.materialize_with_allocation_id(allocation_id, request, "remove_on_worker_stop") + let working_directory_id = Self::working_directory_id(worker_ref, &request.repository.id); + self.materialize_with_working_directory_id( + working_directory_id, + request, + "remove_on_worker_stop", + ) } - fn preallocate( + fn create( &self, request: &WorkingDirectoryRequest, ) -> Result { - let allocation_id = next_allocation_id(&request.repository.id); - self.materialize_with_allocation_id(allocation_id, request, "manual_or_worker_stop") + let working_directory_id = next_working_directory_id(&request.repository.id); + self.materialize_with_working_directory_id( + working_directory_id, + request, + "manual_or_worker_stop", + ) } - fn bind_allocation( + fn bind_working_directory( &self, - allocation_id: &str, + working_directory_id: &str, relative_cwd: Option<&str>, ) -> Result { - validate_allocation_id(allocation_id)?; - let binding = self.read_binding(allocation_id)?; - if binding.allocation.status != WorkingDirectoryStatusKind::Active { + validate_working_directory_id(working_directory_id)?; + let binding = self.read_binding(working_directory_id)?; + if binding.working_directory.status != WorkingDirectoryStatusKind::Active { return Err(WorkingDirectoryDiagnostic::new( "working_directory_not_active", - "working directory allocation is not active", + "working directory working_directory is not active", )); } let cwd = validate_relative_cwd(binding.workspace_root(), relative_cwd)?; Ok(WorkingDirectoryBinding { cwd, ..binding }) } - fn list_allocations(&self) -> Result, WorkingDirectoryDiagnostic> { + fn list_working_directories( + &self, + ) -> Result, WorkingDirectoryDiagnostic> { let root = self.runtime_root.join(WORKING_DIRECTORIES_DIR); let entries = match fs::read_dir(&root) { Ok(entries) => entries, @@ -414,7 +426,7 @@ impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer { Err(_) => { return Err(WorkingDirectoryDiagnostic::new( "working_directory_list_failed", - "failed to list working directory allocations; backend-private path details were omitted", + "failed to list working directory working_directories; backend-private path details were omitted", )); } }; @@ -427,43 +439,46 @@ impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer { if !file_type.is_dir() { continue; } - let allocation_id = entry.file_name().to_string_lossy().to_string(); - if validate_allocation_id(&allocation_id).is_err() { + let working_directory_id = entry.file_name().to_string_lossy().to_string(); + if validate_working_directory_id(&working_directory_id).is_err() { continue; } - if let Ok(status) = self.allocation_status(&allocation_id) { + if let Ok(status) = self.working_directory_status(&working_directory_id) { statuses.push(status); } } - statuses - .sort_by(|left, right| left.summary.allocation_id.cmp(&right.summary.allocation_id)); + statuses.sort_by(|left, right| { + left.summary + .working_directory_id + .cmp(&right.summary.working_directory_id) + }); Ok(statuses) } - fn allocation_status( + fn working_directory_status( &self, - allocation_id: &str, + working_directory_id: &str, ) -> Result { - validate_allocation_id(allocation_id)?; - Ok(self.read_binding(allocation_id)?.status()) + validate_working_directory_id(working_directory_id)?; + Ok(self.read_binding(working_directory_id)?.status()) } - fn cleanup_allocation( + fn cleanup_working_directory( &self, - allocation_id: &str, + working_directory_id: &str, ) -> Result { - validate_allocation_id(allocation_id)?; - let binding = self.read_binding(allocation_id)?; + validate_working_directory_id(working_directory_id)?; + let binding = self.read_binding(working_directory_id)?; self.cleanup(&binding)?; - self.allocation_status(allocation_id) + self.working_directory_status(working_directory_id) } fn cleanup(&self, binding: &WorkingDirectoryBinding) -> Result<(), WorkingDirectoryDiagnostic> { - let mut allocation = binding.allocation.clone(); - let allocation_root = binding.allocation_root.canonicalize().map_err(|_| { + let mut working_directory = binding.working_directory.clone(); + let working_directory_root = binding.working_directory_root.canonicalize().map_err(|_| { WorkingDirectoryDiagnostic::new( "working_directory_cleanup_target_invalid", - "working directory allocation root is unavailable; backend-private path details were omitted", + "working directory working directory root is unavailable; backend-private path details were omitted", ) })?; let workspace_root = binding.workspace_root.canonicalize().map_err(|_| { @@ -472,10 +487,10 @@ impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer { "working directory root is unavailable; backend-private path details were omitted", ) })?; - if !workspace_root.starts_with(&allocation_root) { + if !workspace_root.starts_with(&working_directory_root) { return Err(WorkingDirectoryDiagnostic::new( "working_directory_cleanup_escape_rejected", - "working directory cleanup target is outside the allocation root", + "working directory cleanup target is outside the working directory root", )); } let workspace_root_arg = path_str(&workspace_root)?; @@ -495,16 +510,16 @@ impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer { Ok(()) } }); - allocation.status = if remove_result.is_ok() { + working_directory.status = if remove_result.is_ok() { WorkingDirectoryStatusKind::Removed } else { WorkingDirectoryStatusKind::CleanupPending }; let updated = WorkingDirectoryBinding { - allocation, + working_directory, workspace_root: binding.workspace_root.clone(), cwd: binding.cwd.clone(), - allocation_root: binding.allocation_root.clone(), + working_directory_root: binding.working_directory_root.clone(), source_repository_path: binding.source_repository_path.clone(), }; let _ = self.write_record(&updated); @@ -514,7 +529,7 @@ impl WorkingDirectoryMaterializer for LocalGitWorktreeMaterializer { #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] struct WorkingDirectoryMaterializationRecord { - allocation: WorkingDirectoryAllocation, + working_directory: WorkingDirectory, workspace_root: PathBuf, source_repository_path: PathBuf, } @@ -586,29 +601,31 @@ fn sanitize_path_component(value: &str) -> String { } } -fn next_allocation_id(repository_id: &str) -> String { +fn next_working_directory_id(repository_id: &str) -> String { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|duration| duration.as_millis()) .unwrap_or_default(); - let sequence = NEXT_ALLOCATION_SEQUENCE.fetch_add(1, Ordering::Relaxed); + let sequence = NEXT_WORKING_DIRECTORY_SEQUENCE.fetch_add(1, Ordering::Relaxed); format!( "alloc-{now}-{sequence}-{}", sanitize_path_component(repository_id) ) } -fn validate_allocation_id(allocation_id: &str) -> Result<(), WorkingDirectoryDiagnostic> { - let sanitized = sanitize_path_component(allocation_id); - if allocation_id.is_empty() - || allocation_id != sanitized - || allocation_id.contains(std::path::MAIN_SEPARATOR) - || allocation_id.contains('/') - || allocation_id.contains('\\') +fn validate_working_directory_id( + working_directory_id: &str, +) -> Result<(), WorkingDirectoryDiagnostic> { + let sanitized = sanitize_path_component(working_directory_id); + if working_directory_id.is_empty() + || working_directory_id != sanitized + || working_directory_id.contains(std::path::MAIN_SEPARATOR) + || working_directory_id.contains('/') + || working_directory_id.contains('\\') { return Err(WorkingDirectoryDiagnostic::new( - "working_directory_allocation_id_invalid", - "working directory allocation id is invalid", + "working_directory_id_invalid", + "working directory working_directory id is invalid", )); } Ok(()) @@ -639,7 +656,7 @@ fn validate_relative_cwd( { return Err(WorkingDirectoryDiagnostic::new( "working_directory_relative_cwd_invalid", - "working directory relative_cwd must be a relative path inside the allocation root", + "working directory relative_cwd must be a relative path inside the working directory root", )); } let target = workspace_root @@ -654,7 +671,7 @@ fn validate_relative_cwd( if !target.starts_with(&workspace_root) || !target.is_dir() { return Err(WorkingDirectoryDiagnostic::new( "working_directory_relative_cwd_escape_rejected", - "working directory relative_cwd must resolve to a directory inside the allocation root", + "working directory relative_cwd must resolve to a directory inside the working directory root", )); } Ok(target) @@ -728,16 +745,16 @@ mod tests { "worktree should be detached, got {branch}" ); assert_eq!( - binding.allocation.materializer_kind, + binding.working_directory.materializer_kind, MaterializerKind::LocalGitWorktree ); assert_eq!( - binding.allocation.dirty_state_policy, + binding.working_directory.dirty_state_policy, DirtyStatePolicy::CleanPointOnly ); assert!( binding - .allocation_root() + .working_directory_root() .join(MATERIALIZATION_RECORD) .exists() ); @@ -805,7 +822,7 @@ mod tests { } #[test] - fn preallocated_allocation_binds_safe_relative_cwd_and_lists_without_paths() { + fn working_directory_binds_safe_relative_cwd_and_lists_without_paths() { let repo = create_clean_repo(); fs::create_dir_all(repo.path().join("crates/yoi")).unwrap(); fs::write(repo.path().join("crates/yoi/lib.rs"), "// ok\n").unwrap(); @@ -813,15 +830,18 @@ mod tests { git(repo.path(), &["commit", "-m", "add crate"]); let runtime_root = tempfile::tempdir().unwrap(); let materializer = LocalGitWorktreeMaterializer::new(runtime_root.path()); - let allocation = materializer.preallocate(&request(repo.path())).unwrap(); + let working_directory = materializer.create(&request(repo.path())).unwrap(); let bound = materializer - .bind_allocation(&allocation.allocation.id, Some("crates/yoi")) + .bind_working_directory(&working_directory.working_directory.id, Some("crates/yoi")) .unwrap(); assert_eq!(bound.cwd.file_name().unwrap(), "yoi"); - let listed = materializer.list_allocations().unwrap(); + let listed = materializer.list_working_directories().unwrap(); assert_eq!(listed.len(), 1); - assert_eq!(listed[0].summary.allocation_id, allocation.allocation.id); + assert_eq!( + listed[0].summary.working_directory_id, + working_directory.working_directory.id + ); assert_eq!( listed[0].summary.requested_selector.as_deref(), Some("HEAD") @@ -837,32 +857,35 @@ mod tests { git(repo.path(), &["commit", "-m", "add inside"]); let runtime_root = tempfile::tempdir().unwrap(); let materializer = LocalGitWorktreeMaterializer::new(runtime_root.path()); - let allocation = materializer.preallocate(&request(repo.path())).unwrap(); + let working_directory = materializer.create(&request(repo.path())).unwrap(); assert_eq!( materializer - .bind_allocation(&allocation.allocation.id, Some("/tmp")) + .bind_working_directory(&working_directory.working_directory.id, Some("/tmp")) .unwrap_err() .code, "working_directory_relative_cwd_invalid" ); assert_eq!( materializer - .bind_allocation(&allocation.allocation.id, Some("../outside")) + .bind_working_directory(&working_directory.working_directory.id, Some("../outside")) .unwrap_err() .code, "working_directory_relative_cwd_invalid" ); assert_eq!( materializer - .bind_allocation(&allocation.allocation.id, Some("missing")) + .bind_working_directory(&working_directory.working_directory.id, Some("missing")) .unwrap_err() .code, "working_directory_relative_cwd_unavailable" ); assert_eq!( materializer - .bind_allocation(&allocation.allocation.id, Some("inside/file.txt")) + .bind_working_directory( + &working_directory.working_directory.id, + Some("inside/file.txt") + ) .unwrap_err() .code, "working_directory_relative_cwd_escape_rejected" @@ -870,10 +893,11 @@ mod tests { #[cfg(unix)] { - std::os::unix::fs::symlink("/tmp", allocation.workspace_root().join("escape")).unwrap(); + std::os::unix::fs::symlink("/tmp", working_directory.workspace_root().join("escape")) + .unwrap(); assert_eq!( materializer - .bind_allocation(&allocation.allocation.id, Some("escape")) + .bind_working_directory(&working_directory.working_directory.id, Some("escape")) .unwrap_err() .code, "working_directory_relative_cwd_escape_rejected" @@ -894,8 +918,12 @@ mod tests { materializer.cleanup(&binding).unwrap(); assert!(!workspace_root.exists()); - let raw = - fs::read_to_string(binding.allocation_root().join(MATERIALIZATION_RECORD)).unwrap(); + let raw = fs::read_to_string( + binding + .working_directory_root() + .join(MATERIALIZATION_RECORD), + ) + .unwrap(); assert!(raw.contains("removed")); } } diff --git a/crates/workspace-server/src/companion.rs b/crates/workspace-server/src/companion.rs index 88708336..50e9d721 100644 --- a/crates/workspace-server/src/companion.rs +++ b/crates/workspace-server/src/companion.rs @@ -387,9 +387,9 @@ fn spawn_companion_worker(runtime: &RuntimeRegistry) -> CompanionWorkerState { }, profile: Some(selector), initial_input: None, - working_directory: None, + working_directory_request: None, + resolved_working_directory_request: None, resolved_working_directory: None, - resolved_working_directory_allocation: None, }, ); diff --git a/crates/workspace-server/src/hosts.rs b/crates/workspace-server/src/hosts.rs index 2749b8a7..f1655640 100644 --- a/crates/workspace-server/src/hosts.rs +++ b/crates/workspace-server/src/hosts.rs @@ -13,7 +13,7 @@ use std::{ }; use worker_runtime::catalog::{ ConfigBundleRef, CreateWorkerRequest, ProfileSelector, WorkerDetail as EmbeddedWorkerDetail, - WorkerStatus as EmbeddedWorkerStatus, WorkingDirectoryAllocationClaim, WorkingDirectoryRequest, + WorkerStatus as EmbeddedWorkerStatus, WorkingDirectoryClaim, WorkingDirectoryRequest, WorkingDirectorySummary, }; use worker_runtime::config_bundle::{ @@ -295,15 +295,15 @@ pub struct WorkerSpawnRequest { pub profile: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub initial_input: Option, - /// Optional safe working-directory selector. The Workspace server resolves + /// Optional safe working-directory creation request. 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 working_directory: Option, + pub working_directory_request: Option, #[serde(skip, default)] - pub resolved_working_directory: Option, + pub resolved_working_directory_request: Option, #[serde(skip, default)] - pub resolved_working_directory_allocation: Option, + pub resolved_working_directory: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -1356,8 +1356,8 @@ impl WorkspaceWorkerRuntime for EmbeddedWorkerRuntime { profile, config_bundle, initial_input: request.initial_input.clone(), + working_directory_request: request.resolved_working_directory_request.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) => { @@ -2032,8 +2032,8 @@ impl WorkspaceWorkerRuntime for RemoteWorkerRuntime { profile, config_bundle, initial_input: request.initial_input.clone(), + working_directory_request: request.resolved_working_directory_request.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 { @@ -3191,9 +3191,9 @@ mod tests { }, profile: None, initial_input: None, - working_directory: None, + working_directory_request: None, + resolved_working_directory_request: None, resolved_working_directory: None, - resolved_working_directory_allocation: None, } } @@ -3323,9 +3323,9 @@ mod tests { }, profile: None, initial_input: None, - working_directory: None, + working_directory_request: None, + resolved_working_directory_request: 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, - working_directory: None, + working_directory_request: None, + resolved_working_directory_request: 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, - working_directory: None, + working_directory_request: None, + resolved_working_directory_request: None, resolved_working_directory: None, - resolved_working_directory_allocation: None, }, ) .unwrap(); diff --git a/crates/workspace-server/src/server.rs b/crates/workspace-server/src/server.rs index c3e6c219..d42242d2 100644 --- a/crates/workspace-server/src/server.rs +++ b/crates/workspace-server/src/server.rs @@ -46,7 +46,7 @@ use crate::store::{ControlPlaneStore, WorkspaceRecord}; use crate::{Error, Result}; use worker_runtime::catalog::{ ConfigBundleRef, DirtyStatePolicy, MaterializerKind, ProfileSelector, - RepositorySelector as RuntimeRepositorySelector, WorkingDirectoryAllocationClaim, + RepositorySelector as RuntimeRepositorySelector, WorkingDirectoryClaim, WorkingDirectoryRepository, WorkingDirectoryRequest, WorkingDirectorySummary, }; use worker_runtime::config_bundle::ConfigBundle; @@ -275,7 +275,7 @@ pub fn build_router(api: WorkspaceApi) -> Router { get(scoped_list_working_directories).post(scoped_create_working_directory), ) .route( - "/api/w/{workspace_id}/working-directories/{allocation_id}", + "/api/w/{workspace_id}/working-directories/{working_directory_id}", get(scoped_working_directory_detail).delete(scoped_cleanup_working_directory), ) .route("/api/runtimes", get(list_runtimes)) @@ -626,7 +626,7 @@ pub struct BrowserWorkingDirectoryDetailResponse { #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub struct BrowserWorkerWorkingDirectorySelection { - pub allocation_id: String, + pub working_directory_id: String, #[serde(default)] pub relative_cwd: Option, } @@ -744,7 +744,7 @@ struct ScopedRuntimePath { #[derive(Debug, Deserialize)] struct ScopedWorkingDirectoryPath { workspace_id: String, - allocation_id: String, + working_directory_id: String, } #[derive(Debug, Deserialize)] @@ -903,10 +903,10 @@ async fn scoped_create_working_directory( Json(request): Json, ) -> ApiResult> { validate_workspace_scope(&api, &path.workspace_id)?; - let workspace_request = working_directory_request_for_browser(&api, request)?; + let working_directory_request = working_directory_request_for_browser(&api, request)?; let binding = api .working_directory_materializer - .preallocate(&workspace_request) + .create(&working_directory_request) .map_err(|diagnostic| { ApiError::from(Error::RuntimeOperationFailed { runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(), @@ -928,7 +928,7 @@ async fn scoped_working_directory_detail( validate_workspace_scope(&api, &path.workspace_id)?; let status = api .working_directory_materializer - .allocation_status(&path.allocation_id) + .working_directory_status(&path.working_directory_id) .map_err(|diagnostic| { ApiError::from(Error::RuntimeOperationFailed { runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(), @@ -950,7 +950,7 @@ async fn scoped_cleanup_working_directory( validate_workspace_scope(&api, &path.workspace_id)?; let status = api .working_directory_materializer - .cleanup_allocation(&path.allocation_id) + .cleanup_working_directory(&path.working_directory_id) .map_err(|diagnostic| { ApiError::from(Error::RuntimeOperationFailed { runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(), @@ -1621,28 +1621,28 @@ async fn create_workspace_worker( content: initial_text, }) }; - let resolved_working_directory_allocation = + let resolved_working_directory = request .working_directory - .map(|selection| WorkingDirectoryAllocationClaim { - allocation_id: selection.allocation_id, + .map(|selection| WorkingDirectoryClaim { + working_directory_id: selection.working_directory_id, relative_cwd: selection.relative_cwd, }); - if resolved_working_directory_allocation.is_some() - && request.runtime_id != EMBEDDED_WORKER_RUNTIME_ID - { + if resolved_working_directory.is_some() && request.runtime_id != EMBEDDED_WORKER_RUNTIME_ID { return Err(Error::RuntimeOperationFailed { runtime_id: request.runtime_id.clone(), code: "working_directory_runtime_unsupported".to_string(), - message: "preallocated working directories are only supported by the embedded local runtime in v0".to_string(), + message: + "created working directories are only supported by the embedded local runtime in v0" + .to_string(), } .into()); } - if let Some(allocation) = resolved_working_directory_allocation.as_ref() { + if let Some(working_directory) = resolved_working_directory.as_ref() { api.working_directory_materializer - .bind_allocation( - &allocation.allocation_id, - allocation.relative_cwd.as_deref(), + .bind_working_directory( + &working_directory.working_directory_id, + working_directory.relative_cwd.as_deref(), ) .map_err(|diagnostic| { working_directory_api_error(request.runtime_id.clone(), diagnostic) @@ -1660,9 +1660,9 @@ async fn create_workspace_worker( }, profile: Some(profile_selector), initial_input, - working_directory: None, - resolved_working_directory: None, - resolved_working_directory_allocation, + working_directory_request: None, + resolved_working_directory_request: None, + resolved_working_directory, }, ) .map_err(|err| err.into_error())?; @@ -1751,8 +1751,8 @@ async fn create_runtime_worker( AxumPath(runtime_id): AxumPath, Json(mut request): Json, ) -> ApiResult> { - request.resolved_working_directory = request - .working_directory + request.resolved_working_directory_request = request + .working_directory_request .as_ref() .map(|working_directory| { configured_working_directory_request(&api.config, working_directory) @@ -2713,7 +2713,7 @@ fn working_directory_repository_options( fn working_directory_summaries(api: &WorkspaceApi) -> ApiResult> { api.working_directory_materializer - .list_allocations() + .list_working_directories() .map(|items| items.into_iter().map(|status| status.summary).collect()) .map_err(|diagnostic| { ApiError::from(Error::RuntimeOperationFailed { @@ -3418,8 +3418,8 @@ mod tests { digest: bundle.metadata.digest, }, initial_input: None, + working_directory_request: None, working_directory: None, - working_directory_allocation: None, } } @@ -3435,7 +3435,7 @@ mod tests { } #[tokio::test] - async fn browser_working_directory_preallocate_list_detail_and_cleanup_are_path_safe() { + async fn browser_working_directory_create_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; @@ -3451,7 +3451,7 @@ mod tests { }), ) .await; - let allocation_id = created["item"]["allocation_id"] + let working_directory_id = created["item"]["working_directory_id"] .as_str() .unwrap() .to_string(); @@ -3463,11 +3463,14 @@ mod tests { let list = get_json(app.clone(), &workspace_path).await; assert_eq!(list["items"].as_array().unwrap().len(), 1); - assert_eq!(list["items"][0]["allocation_id"], allocation_id); + assert_eq!( + list["items"][0]["working_directory_id"], + working_directory_id + ); - let detail_path = format!("{workspace_path}/{allocation_id}"); + let detail_path = format!("{workspace_path}/{working_directory_id}"); let detail = get_json(app.clone(), &detail_path).await; - assert_eq!(detail["item"]["allocation_id"], allocation_id); + assert_eq!(detail["item"]["working_directory_id"], working_directory_id); let removed = request_json(app, "DELETE", &detail_path, None, StatusCode::OK).await; assert_eq!(removed["item"]["status"], "removed"); @@ -3489,7 +3492,7 @@ mod tests { }), ) .await; - let allocation_id = created["item"]["allocation_id"].as_str().unwrap(); + let working_directory_id = created["item"]["working_directory_id"].as_str().unwrap(); let response = request_json( app, @@ -3501,7 +3504,7 @@ mod tests { "profile": "builtin:coder", "initial_text": "", "working_directory": { - "allocation_id": allocation_id, + "working_directory_id": working_directory_id, "relative_cwd": "../escape" } })), @@ -3857,7 +3860,7 @@ mod tests { "kind": "run_accepted", "expected_segments": 0 }, - "working_directory": { + "working_directory_request": { "repository_id": TEST_REPOSITORY_ID, "local_path": dir.path().display().to_string() } @@ -3892,7 +3895,7 @@ mod tests { "kind": "run_accepted", "expected_segments": 0 }, - "working_directory": { + "working_directory_request": { "repository_id": TEST_REPOSITORY_ID, "selector": "HEAD" } @@ -4379,9 +4382,9 @@ mod tests { }, profile: None, initial_input: None, - working_directory: None, + working_directory_request: None, + resolved_working_directory_request: None, resolved_working_directory: None, - resolved_working_directory_allocation: None, }, ) .expect("spawn worker"); diff --git a/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte b/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte index 526776d7..a51b61ba 100644 --- a/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte +++ b/web/workspace/src/lib/workspace-sidebar/WorkersNavSection.svelte @@ -48,7 +48,7 @@ let runtimeId = $state(''); let profile = $state('builtin:coder'); let initialText = $state(''); - let workingDirectoryAllocationId = $state(''); + let workingDirectoryId = $state(''); let workingDirectoryRepositoryId = $state(''); let workingDirectorySelector = $state('HEAD'); let relativeCwd = $state(''); @@ -114,7 +114,7 @@ display_name: displayName, profile, initial_text: initialText, - working_directory_allocation_id: workingDirectoryAllocationId, + working_directory_id: workingDirectoryId, working_directory_repository_id: workingDirectoryRepositoryId, working_directory_selector: workingDirectorySelector, relative_cwd: relativeCwd, @@ -122,7 +122,7 @@ runtimeId = form.runtime_id; displayName = form.display_name; profile = form.profile; - workingDirectoryAllocationId = form.working_directory_allocation_id; + workingDirectoryId = form.working_directory_id; workingDirectoryRepositoryId = form.working_directory_repository_id; workingDirectorySelector = form.working_directory_selector; relativeCwd = form.relative_cwd; @@ -158,9 +158,9 @@ const payload = (await response.json()) as BrowserWorkingDirectoryCreateResponse; const items = options?.working_directories ?? []; options = options - ? { ...options, working_directories: [...items.filter((item) => item.allocation_id !== payload.item.allocation_id), payload.item] } + ? { ...options, working_directories: [...items.filter((item) => item.working_directory_id !== payload.item.working_directory_id), payload.item] } : options; - workingDirectoryAllocationId = payload.item.allocation_id; + workingDirectoryId = payload.item.working_directory_id; } catch (err) { submitError = exceptionDisplayError(err, 'working directory create failed'); } finally { @@ -185,7 +185,7 @@ display_name: displayName, profile, initial_text: initialText, - working_directory_allocation_id: workingDirectoryAllocationId, + working_directory_id: workingDirectoryId, working_directory_repository_id: workingDirectoryRepositoryId, working_directory_selector: workingDirectorySelector, relative_cwd: relativeCwd, @@ -279,18 +279,18 @@
Working directory