diff --git a/.yoi/tickets/00001KXDYZ254/item.md b/.yoi/tickets/00001KXDYZ254/item.md index 819ff530..e7dbc307 100644 --- a/.yoi/tickets/00001KXDYZ254/item.md +++ b/.yoi/tickets/00001KXDYZ254/item.md @@ -1,8 +1,8 @@ --- title: 'Restore Worker without backend profile fetch' -state: 'inprogress' +state: 'done' created_at: '2026-07-13T14:44:12Z' -updated_at: '2026-07-13T14:45:04Z' +updated_at: '2026-07-13T14:55:17Z' assignee: null queued_by: 'yoi ticket' queued_at: '2026-07-13T14:45:04Z' diff --git a/.yoi/tickets/00001KXDYZ254/thread.md b/.yoi/tickets/00001KXDYZ254/thread.md index cb6553ed..5e56c1a4 100644 --- a/.yoi/tickets/00001KXDYZ254/thread.md +++ b/.yoi/tickets/00001KXDYZ254/thread.md @@ -39,4 +39,42 @@ Ticket を `yoi ticket` が queued にしました。 State changed to `inprogress`. +--- + + + +## Implementation report + +Changed active Worker restore so it does not fetch backend profile source archives. + +Root cause: +- Worker metadata already contains the resolved manifest snapshot used by `Worker::restore_from_worker_metadata_with_context`. +- `ProfileRuntimeWorkerFactory::restore_controller` still resolved `request.profile_source` before calling restore, so Runtime restart could fail if workspace-server/backend resource endpoint was not available yet. + +Changes: +- Active restore now builds only a minimal builtin fallback manifest/loader and calls `Worker::restore_from_worker_metadata_with_context` directly. +- The metadata `resolved_manifest_snapshot` remains the restore authority. +- Backend profile source archive resolution is deferred to the pending/no-history fallback path only, where fresh Worker recreation still needs a manifest. + +Validation: +- `cargo fmt` +- `cargo check -q` +- `cargo test -q -p worker-runtime --features fs-store,ws-server` +- `cargo test -q -p yoi-workspace-server` +- `cd web/workspace && deno task check && deno task test` +- `git diff --check` +- `nix build .#yoi --no-link` + +Note: README.md had pre-existing unrelated local modifications and was not included. + + +--- + + + +## State changed + +State changed to `done`. + + --- diff --git a/crates/worker-runtime/src/worker_backend.rs b/crates/worker-runtime/src/worker_backend.rs index 772b247e..9c33652c 100644 --- a/crates/worker-runtime/src/worker_backend.rs +++ b/crates/worker-runtime/src/worker_backend.rs @@ -38,7 +38,7 @@ use tokio::runtime::Runtime; use tokio::sync::broadcast; use worker::{ - Worker, WorkerController, WorkerError, WorkerFilesystemAuthority, WorkerHandle, + PromptLoader, Worker, WorkerController, WorkerError, WorkerFilesystemAuthority, WorkerHandle, WorkerWorkspaceContext, WorkspaceId, }; @@ -181,6 +181,16 @@ impl ProfileRuntimeWorkerFactory { ) -> Option> { self.runtime_profile_for_request(&request.request) } + + fn restore_fallback_manifest( + worker_name: &str, + ) -> Result<(manifest::WorkerManifest, PromptLoader), String> { + let mut config = manifest::WorkerManifestConfig::builtin_defaults(); + config.worker.name = Some(worker_name.to_string()); + let manifest = manifest::WorkerManifest::try_from(config) + .map_err(|err| format!("failed to build restore fallback manifest: {err}"))?; + Ok((manifest, PromptLoader::builtins_only())) + } async fn resolve_profile_source_archive( &self, source: &ProfileSourceArchiveSource, @@ -397,7 +407,6 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory { request: WorkerExecutionRestoreRequest, ) -> Result { let worker_name = Self::runtime_worker_name_for_ref(&request.worker_ref); - let profile = self.runtime_profile_for_request(&request.request); let worker_root = request .working_directory .as_ref() @@ -416,21 +425,7 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory { let workspace_backend_ref = RuntimeWorkspaceBackendRef::from_working_directory(request.working_directory.as_ref()); let workspace_context = workspace_backend_ref.worker_context(); - let selector = profile.as_deref().unwrap_or("builtin:default"); - let archive = self - .resolve_profile_source_archive(&request.request.profile_source) - .await?; - let (mut manifest, loader) = { - let manifest = archive - .resolve_profile(selector, &worker_root, &worker_name) - .map_err(|err| format!("failed to resolve profile source archive: {err}"))?; - worker::entrypoint::resolve_runtime_profile_manifest_from_manifest( - manifest, - &worker_root, - &worker_name, - )? - }; - manifest.worker.name = worker_name.clone(); + let (manifest, loader) = Self::restore_fallback_manifest(&worker_name)?; let store_dir = self.store_dir()?; let session_store = FsStore::new(&store_dir).map_err(|err| { @@ -462,6 +457,24 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory { Err(WorkerError::WorkerMetadataPending { .. }) if request.request.initial_input.is_none() => { + let profile = self.runtime_profile_for_request(&request.request); + let selector = profile.as_deref().unwrap_or("builtin:default"); + let archive = self + .resolve_profile_source_archive(&request.request.profile_source) + .await?; + let (mut pending_manifest, pending_loader) = { + let manifest = archive + .resolve_profile(selector, &worker_root, &worker_name) + .map_err(|err| { + format!("failed to resolve profile source archive: {err}") + })?; + worker::entrypoint::resolve_runtime_profile_manifest_from_manifest( + manifest, + &worker_root, + &worker_name, + )? + }; + pending_manifest.worker.name = worker_name.clone(); let session_store = FsStore::new(&store_dir).map_err(|err| { format!( "failed to initialize session store at {}: {err}", @@ -477,9 +490,9 @@ impl RuntimeWorkerFactory for ProfileRuntimeWorkerFactory { })?; let store = CombinedStore::new(session_store, worker_metadata_store); Worker::from_manifest_with_context( - manifest, + pending_manifest, store, - loader, + pending_loader, workspace_context, filesystem_authority, )