From 00d6c3a7c4084d957f7822b2aa3c3aa544945a1b Mon Sep 17 00:00:00 2001 From: Hare Date: Tue, 21 Jul 2026 12:07:38 +0900 Subject: [PATCH] fix: remove direct worker local workspace discovery --- crates/worker/src/entrypoint.rs | 422 +++++--------------------------- 1 file changed, 68 insertions(+), 354 deletions(-) diff --git a/crates/worker/src/entrypoint.rs b/crates/worker/src/entrypoint.rs index cb84ec7a..3134d8cf 100644 --- a/crates/worker/src/entrypoint.rs +++ b/crates/worker/src/entrypoint.rs @@ -4,24 +4,18 @@ use std::process::ExitCode; use crate::{ PromptLoader, Worker, WorkerController, WorkerFilesystemAuthority, WorkerWorkspaceContext, - WorkspaceId, }; use clap::{CommandFactory, FromArgMatches, Parser}; -use manifest::{ - Permission, ProfileResolveOptions, ProfileResolver, ProfileSelector, ScopeConfig, ScopeRule, - WorkerManifest, WorkerManifestConfig, paths, - plugin::{PluginDiscoveryOptions, resolve_plugin_config_for_startup}, -}; +use manifest::{Permission, ScopeConfig, ScopeRule, WorkerManifest, WorkerManifestConfig, paths}; use session_store::{CombinedStore, FsWorkerStore, WorkerMetadataStore}; use session_store::{FsStore, SegmentId, Store}; use ticket::config::TicketRole; #[derive(Debug, Parser)] -#[command(about = "Spawn a Worker process from a profile or a single manifest file")] +#[command(about = "Spawn a Worker process from a manifest or builtin defaults")] struct Cli { - /// Profile to evaluate. Accepts an explicit path, `path:`, a - /// discovered profile name, `default`, or a source-qualified name such as - /// `project:coder`. + /// Legacy profile selector. Direct worker startup no longer resolves + /// workspace-local profiles; pass a Backend-resolved manifest instead. #[arg( long, value_name = "PROFILE", @@ -29,8 +23,8 @@ struct Cli { )] profile: Option, - /// Runtime workspace root for profile discovery, default Worker naming, and process context. - /// Defaults to the current directory. + /// Process root/cwd context for direct worker startup. This is not Workspace + /// authority and does not enable `.yoi` discovery. Defaults to current dir. #[arg(long, value_name = "PATH")] workspace: Option, @@ -106,22 +100,8 @@ fn runtime_workspace_root(cli: &Cli) -> Result { } } -fn runtime_workspace_context(workspace_root: &Path) -> WorkerWorkspaceContext { - WorkerWorkspaceContext::local_filesystem(read_workspace_id_hint(workspace_root)) -} - -fn read_workspace_id_hint(workspace_root: &Path) -> Option { - let path = workspace_root.join(".yoi/workspace.toml"); - let contents = std::fs::read_to_string(path).ok()?; - let value = toml::from_str::(&contents).ok()?; - let id = value.get("id")?.as_str()?.to_string(); - match WorkspaceId::new(id) { - Ok(id) => Some(id), - Err(err) => { - tracing::warn!("ignoring invalid workspace id in .yoi/workspace.toml: {err}"); - None - } - } +fn runtime_workspace_context() -> WorkerWorkspaceContext { + WorkerWorkspaceContext::no_workspace() } fn runtime_worker_name(cli: &Cli, workspace_root: &Path) -> String { @@ -158,63 +138,39 @@ fn sanitise_worker_name(raw: &str) -> String { } fn resolve_manifest(cli: &Cli) -> Result<(WorkerManifest, PromptLoader), String> { - resolve_manifest_with_profile_loader(cli, load_profile) -} - -fn resolve_manifest_with_profile_loader( - cli: &Cli, - load_profile_fn: F, -) -> Result<(WorkerManifest, PromptLoader), String> -where - F: FnOnce(&ProfileSelector, &Path, &str) -> Result<(WorkerManifest, PromptLoader), String>, -{ - let workspace_root = runtime_workspace_root(cli)?; - let runtime_worker_name = runtime_worker_name(cli, &workspace_root); - let ((mut manifest, loader), manifest_source) = - if let Some(config_json) = cli.spawn_config_json.as_deref() { - ( - load_spawn_config_json(config_json)?, - ManifestSource::SpawnConfig, - ) - } else if let Some(profile) = &cli.profile { - let selector = ProfileSelector::parse_cli(profile); - ( - load_profile_fn(&selector, &workspace_root, &runtime_worker_name)?, - ManifestSource::ProfileLaunch, - ) - } else if let Some(path) = &cli.manifest { - ( - load_single_manifest(path, cli.worker.as_deref(), &runtime_worker_name)?, - ManifestSource::ManifestFile, - ) - } else { - if cli.project.is_some() { - return Err( - "--project is no longer supported; normal startup uses profile discovery/default, \ - and --manifest is the only one-file manifest mode" + let process_root = runtime_workspace_root(cli)?; + let runtime_worker_name = runtime_worker_name(cli, &process_root); + let ((mut manifest, loader), apply_direct_launch_policy) = if let Some(config_json) = + cli.spawn_config_json.as_deref() + { + (load_spawn_config_json(config_json)?, false) + } else if let Some(profile) = &cli.profile { + return Err(format!( + "--profile {profile} requires Backend-resolved manifest authority; direct worker startup does not discover workspace-local profiles" + )); + } else if let Some(path) = &cli.manifest { + ( + load_single_manifest(path, cli.worker.as_deref(), &runtime_worker_name)?, + false, + ) + } else { + if cli.project.is_some() { + return Err( + "--project is no longer supported; direct worker startup uses --manifest, \ + --spawn-config-json, or builtin defaults only" .to_string(), ); - } - let selector = ProfileSelector::Default; - ( - load_profile_fn(&selector, &workspace_root, &runtime_worker_name)?, - ManifestSource::ProfileLaunch, - ) - }; + } + (load_builtin_default_manifest(&runtime_worker_name)?, true) + }; - if manifest_source == ManifestSource::ProfileLaunch { - apply_profile_launch_policy(&mut manifest, &workspace_root, cli.ticket_role.as_deref())?; + if apply_direct_launch_policy { + apply_profile_launch_policy(&mut manifest, &process_root, cli.ticket_role.as_deref())?; } apply_session_restore_overrides(&mut manifest, cli)?; - apply_plugin_resolution_plan(&mut manifest, &workspace_root); Ok((manifest, loader)) } -fn apply_plugin_resolution_plan(manifest: &mut WorkerManifest, workspace_root: &Path) { - let options = PluginDiscoveryOptions::new(workspace_root); - manifest.plugins = resolve_plugin_config_for_startup(&manifest.plugins, &options); -} - fn apply_session_restore_overrides(manifest: &mut WorkerManifest, cli: &Cli) -> Result<(), String> { if let Some(worker_name) = cli.worker.as_deref() { manifest.worker.name = worker_name.to_string(); @@ -230,20 +186,14 @@ fn load_spawn_config_json(config_json: &str) -> Result<(WorkerManifest, PromptLo Ok((manifest, PromptLoader::builtins_only())) } -fn load_profile( - selector: &ProfileSelector, - workspace_root: &Path, +fn load_builtin_default_manifest( worker_name: &str, ) -> Result<(WorkerManifest, PromptLoader), String> { - let resolver = ProfileResolver::new().with_workspace_base(workspace_root); - let options = ProfileResolveOptions::with_worker_name(worker_name); - let resolved = resolver.resolve(selector, options).map_err(|e| { - format!( - "failed to resolve profile {}: {e}", - selector.display_label() - ) - })?; - Ok((resolved.manifest, PromptLoader::builtins_only())) + let mut config = WorkerManifestConfig::builtin_defaults(); + config.worker.name = Some(worker_name.to_string()); + let manifest = WorkerManifest::try_from(config) + .map_err(|e| format!("failed to resolve builtin worker defaults: {e}"))?; + Ok((manifest, PromptLoader::builtins_only())) } pub fn resolve_runtime_profile_manifest( @@ -268,7 +218,7 @@ pub fn resolve_runtime_profile_manifest_from_manifest( apply_profile_launch_policy(&mut manifest, workspace_root, None)?; // Do not run plugin discovery here: runtime-created Workers receive their // resolved manifest/profile archive from Backend authority, not by scanning - // the materialized workdir's `.yoi/plugins`. + // materialized workdir-local plugin stores. Ok((manifest, PromptLoader::builtins_only())) } @@ -282,7 +232,7 @@ pub fn resolve_runtime_profile_manifest_from_manifest_without_filesystem( } manifest.scope = ScopeConfig::default(); manifest.delegation_scope = ScopeConfig::default(); - // Same as the filesystem-capable runtime path: no local `.yoi` discovery. + // Same as the filesystem-capable runtime path: no local discovery. Ok((manifest, PromptLoader::builtins_only())) } @@ -327,13 +277,6 @@ fn load_single_manifest( Ok((manifest, PromptLoader::builtins_only())) } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ManifestSource { - ProfileLaunch, - ManifestFile, - SpawnConfig, -} - fn read_rule(target: PathBuf) -> ScopeRule { ScopeRule { target, @@ -547,7 +490,7 @@ async fn run_cli_inner(cli: Cli) -> ExitCode { let store = CombinedStore::new(session_store, worker_metadata_store); let filesystem_authority = WorkerFilesystemAuthority::local(workspace_root.clone(), cwd.clone()); - let workspace_context = runtime_workspace_context(&workspace_root); + let workspace_context = runtime_workspace_context(); let mut worker = if cli.adopt { let callback = match cli.callback.clone() { @@ -862,226 +805,46 @@ permission = "write" } #[test] - fn profile_launch_preserves_workspace_override_scope_allow_in_final_manifest() { + fn profile_resolution_is_rejected_for_direct_worker_startup() { + let cli = Cli::try_parse_from(["yoi worker", "--profile", "project:coder"]).unwrap(); + + let err = resolve_manifest(&cli).unwrap_err(); + + assert!( + err.contains("--profile project:coder requires Backend-resolved manifest authority") + ); + } + + #[test] + fn normal_startup_uses_builtin_defaults_without_local_profile_discovery() { let tmp = TempDir::new().unwrap(); let workspace = tmp.path().join("runtime-workspace"); - let external = tmp.path().join("external-readable"); let yoi_dir = workspace.join(".yoi"); - std::fs::create_dir_all(&workspace).unwrap(); - std::fs::create_dir_all(&external).unwrap(); std::fs::create_dir_all(&yoi_dir).unwrap(); write( &yoi_dir.join("override.local.toml"), - &format!( - r#" -[[scope.allow]] -target = "{}" -permission = "read" -recursive = true -"#, - external.display() - ), - ); - let profile = tmp.path().join("profile.toml"); - write( - &profile, r#" -slug = "override-scope" +[worker] +name = "from-local-override" -[model] -scheme = "anthropic" -model_id = "test-model" +[engine] +language = "override" "#, ); - let cli = Cli::try_parse_from([ - "yoi worker", - "--workspace", - workspace.to_str().unwrap(), - "--profile", - profile.to_str().unwrap(), - ]) - .unwrap(); - - let (manifest, _loader) = resolve_manifest(&cli).unwrap(); - let snapshot = serde_json::to_value(&manifest).unwrap(); - let snapshot_scope: ScopeConfig = - serde_json::from_value(snapshot["scope"].clone()).unwrap(); - - assert_scope_contains(&manifest.scope.allow, &external, Permission::Read); - assert_scope_contains(&manifest.scope.allow, &workspace, Permission::Write); - assert_scope_contains( - &manifest.scope.deny, - &workspace.join(".worktree"), - Permission::Write, - ); - assert_scope_contains(&snapshot_scope.allow, &external, Permission::Read); - assert_scope_contains(&snapshot_scope.allow, &workspace, Permission::Write); - assert_scope_contains( - &snapshot_scope.deny, - &workspace.join(".worktree"), - Permission::Write, - ); - } - - #[test] - fn profile_uses_selected_profile() { - let tmp = TempDir::new().unwrap(); - let profile = tmp.path().join("profile.toml"); - let cli = Cli::try_parse_from([ - "yoi worker", - "--profile", - profile.to_str().unwrap(), - "--worker", - "from-profile-name", - ]) - .unwrap(); - let mut called = false; - - let (manifest, loader) = - resolve_manifest_with_profile_loader(&cli, |selector, _workspace_root, worker_name| { - called = true; - assert_eq!(selector, &ProfileSelector::path(profile.clone())); - assert_eq!(worker_name, "from-profile-name"); - let mut manifest = - WorkerManifest::from_toml(&manifest_toml("from-profile", tmp.path())).unwrap(); - manifest.worker.name = worker_name.to_string(); - Ok((manifest, PromptLoader::builtins_only())) - }) - .unwrap(); - - assert!(called); - assert_eq!(manifest.worker.name, "from-profile-name"); - assert!(loader.user_dir().is_none()); - assert!(loader.workspace_dir().is_none()); - } - - #[test] - fn profile_accepts_source_qualified_discovered_name() { - let tmp = TempDir::new().unwrap(); - let cli = Cli::try_parse_from([ - "yoi worker", - "--profile", - "project:coder", - "--worker", - "from-profile-name", - ]) - .unwrap(); - let mut called = false; - - let (manifest, _loader) = - resolve_manifest_with_profile_loader(&cli, |selector, _workspace_root, worker_name| { - called = true; - assert_eq!( - selector, - &ProfileSelector::source_named( - manifest::ProfileRegistrySource::Project, - "coder" - ) - ); - let mut manifest = - WorkerManifest::from_toml(&manifest_toml("from-profile", tmp.path())).unwrap(); - manifest.worker.name = worker_name.to_string(); - Ok((manifest, PromptLoader::builtins_only())) - }) - .unwrap(); - - assert!(called); - assert_eq!(manifest.worker.name, "from-profile-name"); - } - - #[test] - fn profile_without_explicit_worker_uses_workspace_basename_not_selector() { - let tmp = TempDir::new().unwrap(); - let workspace = tmp.path().join("other-workspace"); - std::fs::create_dir(&workspace).unwrap(); - let cli = Cli::try_parse_from([ - "yoi worker", - "--workspace", - workspace.to_str().unwrap(), - "--profile", - "project:companion", - ]) - .unwrap(); - let mut called = false; - - let (manifest, _loader) = - resolve_manifest_with_profile_loader(&cli, |selector, workspace_root, worker_name| { - called = true; - assert_eq!( - selector, - &ProfileSelector::source_named( - manifest::ProfileRegistrySource::Project, - "companion" - ) - ); - assert_eq!(workspace_root, workspace.as_path()); - assert_eq!(worker_name, "other-workspace"); - let mut manifest = - WorkerManifest::from_toml(&manifest_toml("profile-selector-name", tmp.path())) - .unwrap(); - manifest.worker.name = worker_name.to_string(); - Ok((manifest, PromptLoader::builtins_only())) - }) - .unwrap(); - - assert!(called); - assert_eq!(manifest.worker.name, "other-workspace"); - } - - #[test] - fn normal_startup_uses_default_profile() { - let tmp = TempDir::new().unwrap(); - let workspace = tmp.path().join("runtime-workspace"); - std::fs::create_dir(&workspace).unwrap(); let cli = Cli::try_parse_from(["yoi worker", "--workspace", workspace.to_str().unwrap()]) .unwrap(); - let mut called = false; - let (manifest, _loader) = - resolve_manifest_with_profile_loader(&cli, |selector, _workspace_root, worker_name| { - called = true; - assert_eq!(selector, &ProfileSelector::Default); - assert_eq!(worker_name, "runtime-workspace"); - let mut manifest = - WorkerManifest::from_toml(&manifest_toml("from-default-profile", tmp.path())) - .unwrap(); - manifest.worker.name = worker_name.to_string(); - Ok((manifest, PromptLoader::builtins_only())) - }) - .unwrap(); + let (manifest, loader) = resolve_manifest(&cli).unwrap(); - assert!(called); assert_eq!(manifest.worker.name, "runtime-workspace"); - assert_eq!(manifest.scope.allow.len(), 2); - assert_scope_contains(&manifest.scope.allow, tmp.path(), Permission::Write); + assert_ne!(manifest.engine.language, "override"); + assert!(loader.user_dir().is_none()); + assert!(loader.workspace_dir().is_none()); assert_scope_contains(&manifest.scope.allow, &workspace, Permission::Write); - assert_eq!(manifest.scope.deny.len(), 1); - assert_scope_contains( - &manifest.scope.deny, - &tmp.path().join("runtime-workspace/.worktree"), - Permission::Write, - ); - assert_eq!(manifest.delegation_scope.allow.len(), 2); - assert_eq!( - manifest.delegation_scope.allow[0].target, - tmp.path().join("runtime-workspace") - ); - assert_eq!( - manifest.delegation_scope.allow[0].permission, - Permission::Read - ); - assert_eq!( - manifest.delegation_scope.allow[1].target, - tmp.path().join("runtime-workspace/.worktree") - ); - assert_eq!( - manifest.delegation_scope.allow[1].permission, - Permission::Write - ); } #[test] - fn orchestrator_profile_launch_gets_read_root_and_worktree_delegation_from_launch_policy() { + fn orchestrator_direct_startup_gets_read_root_and_worktree_delegation_from_launch_policy() { let tmp = TempDir::new().unwrap(); let workspace = tmp.path().join("original-workspace"); std::fs::create_dir(&workspace).unwrap(); @@ -1089,41 +852,17 @@ model_id = "test-model" "yoi worker", "--workspace", workspace.to_str().unwrap(), - "--profile", - "builtin:orchestrator", "--ticket-role", "orchestrator", ]) .unwrap(); - let (manifest, _loader) = - resolve_manifest_with_profile_loader(&cli, |selector, _workspace_root, worker_name| { - assert_eq!( - selector, - &ProfileSelector::source_named( - manifest::ProfileRegistrySource::Builtin, - "orchestrator" - ) - ); - let mut manifest = WorkerManifest::from_toml(&manifest_toml( - "from-orchestrator-profile", - tmp.path(), - )) - .unwrap(); - manifest.worker.name = worker_name.to_string(); - Ok((manifest, PromptLoader::builtins_only())) - }) - .unwrap(); + let (manifest, _loader) = resolve_manifest(&cli).unwrap(); - assert_eq!(manifest.scope.allow.len(), 2); - assert_scope_contains(&manifest.scope.allow, tmp.path(), Permission::Write); assert_scope_contains(&manifest.scope.allow, &workspace, Permission::Read); assert!(manifest.scope.deny.is_empty()); assert_eq!(manifest.delegation_scope.allow.len(), 2); - assert_eq!( - manifest.delegation_scope.allow[0].target, - tmp.path().join("original-workspace") - ); + assert_eq!(manifest.delegation_scope.allow[0].target, workspace); assert_eq!( manifest.delegation_scope.allow[0].permission, Permission::Read @@ -1136,23 +875,12 @@ model_id = "test-model" manifest.delegation_scope.allow[1].permission, Permission::Write ); - assert!( - !manifest - .delegation_scope - .allow - .iter() - .any(|rule| rule.target == tmp.path().join("original-workspace") - && rule.permission == Permission::Write) - ); } #[test] fn project_flag_no_longer_enables_ambient_manifest_cascade() { let cli = Cli::try_parse_from(["yoi worker", "--project", "."]).unwrap(); - let err = resolve_manifest_with_profile_loader(&cli, |_, _, _| { - panic!("default profile loader must not run when deprecated --project is present") - }) - .unwrap_err(); + let err = resolve_manifest(&cli).unwrap_err(); assert!(err.contains("--project is no longer supported")); } @@ -1234,25 +962,11 @@ permission = "write" } #[test] - fn worker_flag_with_no_manifest_creates_from_default_profile_with_typed_name() { - let tmp = TempDir::new().unwrap(); + fn worker_flag_with_no_manifest_creates_from_builtin_defaults_with_typed_name() { let cli = Cli::try_parse_from(["yoi worker", "--worker", "agent"]).unwrap(); - let mut called = false; - let (manifest, _loader) = - resolve_manifest_with_profile_loader(&cli, |selector, _workspace_root, worker_name| { - called = true; - assert_eq!(selector, &ProfileSelector::Default); - assert_eq!(worker_name, "agent"); - let mut manifest = - WorkerManifest::from_toml(&manifest_toml("from-default-profile", tmp.path())) - .unwrap(); - manifest.worker.name = worker_name.to_string(); - Ok((manifest, PromptLoader::builtins_only())) - }) - .unwrap(); + let (manifest, _loader) = resolve_manifest(&cli).unwrap(); - assert!(called); assert_eq!(manifest.worker.name, "agent"); }