feat(worker): separate restored candidates, repair, and activation
This commit is contained in:
@@ -9,6 +9,7 @@ use crate::PromptCatalogSource;
|
||||
use crate::controller::{
|
||||
ShutdownReceiver, WorkerController, WorkerControllerTransport, WorkerHandle,
|
||||
};
|
||||
pub use crate::worker::{PreparedRestoredWorker, RestoreCandidateWorkdirCleanup};
|
||||
use crate::worker::{Worker, WorkerError, WorkerFilesystemAuthority, WorkerWorkspaceContext};
|
||||
use manifest::WorkerManifest;
|
||||
|
||||
@@ -168,6 +169,38 @@ where
|
||||
Ok(PreparedWorker::new(worker, self.layout, self.transport))
|
||||
}
|
||||
|
||||
/// Reconstruct a saved materialized Worker without allocation, attribution
|
||||
/// repair, Feature installation, or controller exposure. The caller owns the
|
||||
/// later controller layout/transport and must activate before starting it.
|
||||
pub async fn prepare_restored_candidate(
|
||||
self,
|
||||
worker_name: &str,
|
||||
) -> Result<PreparedRestoredWorker<St>, WorkerBootstrapError> {
|
||||
let mut candidate =
|
||||
Worker::prepare_restored_from_worker_metadata_with_context_and_model_client(
|
||||
worker_name,
|
||||
self.manifest,
|
||||
self.store,
|
||||
self.prompt_catalog,
|
||||
self.workspace_context,
|
||||
self.filesystem_authority,
|
||||
self.model_client,
|
||||
)
|
||||
.await
|
||||
.map_err(WorkerBootstrapError::Worker)?;
|
||||
if let Some(workdir_session) = self.workdir_session {
|
||||
candidate
|
||||
.worker_mut()
|
||||
.map_err(WorkerBootstrapError::Worker)?
|
||||
.bind_single_workdir_session(Some(workdir_session));
|
||||
}
|
||||
candidate
|
||||
.worker_mut()
|
||||
.map_err(WorkerBootstrapError::Worker)?
|
||||
.bind_subjektiv_host(self.subjektiv_host);
|
||||
Ok(candidate)
|
||||
}
|
||||
|
||||
pub async fn prepare_restored(
|
||||
self,
|
||||
worker_name: &str,
|
||||
|
||||
@@ -689,6 +689,35 @@ pub enum WorkerControllerTransport {
|
||||
pub struct WorkerController;
|
||||
|
||||
impl WorkerController {
|
||||
/// Explicit recovery of an unmaterialized Session head. This installs the
|
||||
/// saved Feature composition needed to produce the seed, but creates no
|
||||
/// Controller, input channel, socket, observer, or queue consumer. The caller
|
||||
/// owns the same operation's Feature/session/allocation cleanup on every exit.
|
||||
pub async fn recover_pending_head<C, St>(
|
||||
worker: &mut Worker<C, St>,
|
||||
runtime_base: &Path,
|
||||
) -> std::io::Result<()>
|
||||
where
|
||||
C: LlmClient + Clone + 'static,
|
||||
St: Store + WorkerMetadataStore + Clone + Send + Sync + 'static,
|
||||
{
|
||||
if worker.needs_initial_session_head_materialization() {
|
||||
worker
|
||||
.validate_subjektiv_body_publication()
|
||||
.map_err(std::io::Error::other)?;
|
||||
let registry = SpawnedWorkerRegistry::new_internal(
|
||||
worker.manifest().worker.name.clone(),
|
||||
worker.scope().clone(),
|
||||
);
|
||||
register_worker_tools(worker, runtime_base.to_path_buf(), registry, None, None).await?;
|
||||
worker
|
||||
.materialize_durable_session_head()
|
||||
.await
|
||||
.map_err(std::io::Error::other)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn spawn<C, St>(
|
||||
worker: Worker<C, St>,
|
||||
runtime_base: &Path,
|
||||
|
||||
@@ -1066,7 +1066,8 @@ mod tests {
|
||||
use tokio::net::UnixListener;
|
||||
|
||||
use crate::runtime::dir::RuntimeDir;
|
||||
use crate::runtime::worker_allocation::test_util::RuntimeDirSandbox;
|
||||
|
||||
use crate::restore_test_environment::require_restore_test_environment;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FailTargetPeerStore {
|
||||
@@ -1110,11 +1111,13 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn state_backed_visibility_and_restore_planning() {
|
||||
let root = TempDir::new().unwrap();
|
||||
require_restore_test_environment();
|
||||
let root = tempfile::tempdir_in(".").unwrap();
|
||||
let store_dir = root.path().join("store");
|
||||
let runtime_base = root.path().join("runtime");
|
||||
// Bind, durable child metadata, and discovery probes all use this same
|
||||
// short reference into the tree still owned by root. No cwd mutation.
|
||||
let runtime_base = Path::new(root.path().file_name().unwrap()).join("runtime");
|
||||
std::fs::create_dir_all(&runtime_base).unwrap();
|
||||
let _runtime_sandbox = RuntimeDirSandbox::new(&runtime_base);
|
||||
|
||||
let store = FsWorkerStore::new(&store_dir).unwrap();
|
||||
let session_id = new_session_id();
|
||||
|
||||
@@ -20,6 +20,9 @@ mod metadata_lifecycle;
|
||||
pub mod model_client;
|
||||
mod paste_artifact_tool;
|
||||
pub mod prompt;
|
||||
#[cfg(test)]
|
||||
#[path = "../../../tests/support/restore_environment.rs"]
|
||||
mod restore_test_environment;
|
||||
pub mod runtime;
|
||||
pub mod runtime_command;
|
||||
pub mod segment_log_sink;
|
||||
|
||||
+1004
-30
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -150,6 +151,19 @@ impl LlmClient for MockClient {
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[path = "../../../tests/support/restore_environment.rs"]
|
||||
mod restore_test_environment;
|
||||
use restore_test_environment::require_restore_test_environment;
|
||||
|
||||
fn owned_relative_runtime_base() -> (tempfile::TempDir, PathBuf) {
|
||||
require_restore_test_environment();
|
||||
let temp = tempfile::tempdir_in(".").unwrap();
|
||||
// The relative reference and the TempDir name the same owned tree, without
|
||||
// putting the checkout prefix into sockaddr_un or changing process cwd.
|
||||
let runtime_base = Path::new(temp.path().file_name().unwrap()).join("runtime");
|
||||
(temp, runtime_base)
|
||||
}
|
||||
|
||||
fn simple_text_events() -> Vec<LlmEvent> {
|
||||
vec![
|
||||
LlmEvent::text_block_start(0),
|
||||
@@ -295,6 +309,7 @@ async fn make_worker_with_manifest_and_workspace_context(
|
||||
manifest: WorkerManifest,
|
||||
workspace_context: WorkerWorkspaceContext,
|
||||
) -> (Worker<MockClient, TestStore>, std::path::PathBuf) {
|
||||
require_restore_test_environment();
|
||||
let store_tmp = tempfile::tempdir().unwrap();
|
||||
let store = CombinedStore::new(
|
||||
FsStore::new(store_tmp.path()).unwrap(),
|
||||
@@ -320,18 +335,21 @@ async fn make_worker_with_manifest_and_workspace_context(
|
||||
}
|
||||
|
||||
async fn spawn_controller(worker: Worker<MockClient, TestStore>) -> WorkerHandle {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let runtime_base = tmp.path().to_owned();
|
||||
std::mem::forget(tmp);
|
||||
let (handle, _shutdown_rx) = WorkerController::spawn(worker, &runtime_base)
|
||||
let (tmp, runtime_base) = owned_relative_runtime_base();
|
||||
let (handle, shutdown_rx) = WorkerController::spawn(worker, &runtime_base)
|
||||
.await
|
||||
.unwrap();
|
||||
// Retain ownership until controller shutdown (or test-runtime teardown).
|
||||
tokio::spawn(async move {
|
||||
let _ = shutdown_rx.await;
|
||||
drop(tmp);
|
||||
});
|
||||
handle
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn canonical_worker_handle_uploads_and_submits_attachment() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let (root, runtime_base) = owned_relative_runtime_base();
|
||||
let manifest = WorkerManifest::from_toml(MANIFEST_TOML).unwrap();
|
||||
let session = session_store::WorkerSessionStore::new(root.path().join("session")).unwrap();
|
||||
let store = CombinedStore::new(
|
||||
@@ -353,7 +371,7 @@ async fn canonical_worker_handle_uploads_and_submits_attachment() {
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let (handle, shutdown_rx) = WorkerController::spawn(worker, &root.path().join("runtime"))
|
||||
let (handle, shutdown_rx) = WorkerController::spawn(worker, &runtime_base)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut rx = handle.subscribe();
|
||||
@@ -1761,11 +1779,12 @@ permission = "write"
|
||||
"#;
|
||||
let client = MockClient::new(simple_text_events());
|
||||
let worker = make_worker_with_pwd_and_manifest(client, manifest).await.0;
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let result = WorkerController::spawn(worker, tmp.path()).await;
|
||||
let (_tmp, runtime_base) = owned_relative_runtime_base();
|
||||
let result = WorkerController::spawn(worker, &runtime_base).await;
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"feature exposure must not imply delegation authority"
|
||||
"feature exposure must not imply delegation authority: {:?}",
|
||||
result.as_ref().err()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user