fix: preserve standalone scope conflict details
This commit is contained in:
@@ -4,6 +4,7 @@ use std::time::Duration;
|
|||||||
use agen::llm_client::client::LlmClient;
|
use agen::llm_client::client::LlmClient;
|
||||||
use client::Client;
|
use client::Client;
|
||||||
use client::transport::in_process::{Peer as InProcessPeer, Socket as InProcessSocket};
|
use client::transport::in_process::{Peer as InProcessPeer, Socket as InProcessSocket};
|
||||||
|
use manifest::ScopeRule;
|
||||||
use protocol::stream::{decode_method, encode_event};
|
use protocol::stream::{decode_method, encode_event};
|
||||||
use protocol::{Event, Method, WorkerId};
|
use protocol::{Event, Method, WorkerId};
|
||||||
use session_store::{
|
use session_store::{
|
||||||
@@ -18,6 +19,7 @@ use worker::ipc::protocol_session::{
|
|||||||
WorkerProtocolSessionStreams, dispatch_worker_protocol_method, live_log_entry_event,
|
WorkerProtocolSessionStreams, dispatch_worker_protocol_method, live_log_entry_event,
|
||||||
subscribe_worker_protocol_session,
|
subscribe_worker_protocol_session,
|
||||||
};
|
};
|
||||||
|
use worker::runtime::worker_allocation::ScopeLockError;
|
||||||
use worker::{BootstrappedWorker, WorkerError, WorkerFilesystemAuthority, WorkerWorkspaceContext};
|
use worker::{BootstrappedWorker, WorkerError, WorkerFilesystemAuthority, WorkerWorkspaceContext};
|
||||||
|
|
||||||
use crate::launch::ResolvedStandaloneLaunch;
|
use crate::launch::ResolvedStandaloneLaunch;
|
||||||
@@ -43,7 +45,7 @@ pub struct StandaloneHost {
|
|||||||
lease: Option<StandaloneWorkerLease>,
|
lease: Option<StandaloneWorkerLease>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
|
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||||
pub enum StandaloneStartupError {
|
pub enum StandaloneStartupError {
|
||||||
#[error("the standalone state store could not be opened or validated")]
|
#[error("the standalone state store could not be opened or validated")]
|
||||||
StateStore,
|
StateStore,
|
||||||
@@ -53,6 +55,16 @@ pub enum StandaloneStartupError {
|
|||||||
LeaseLivenessUnknown,
|
LeaseLivenessUnknown,
|
||||||
#[error("the standalone Worker working directory is unavailable or changed")]
|
#[error("the standalone Worker working directory is unavailable or changed")]
|
||||||
WorkingDirectoryUnavailable,
|
WorkingDirectoryUnavailable,
|
||||||
|
#[error(
|
||||||
|
"requested scope `{}` conflicts with worker allocation `{competitor}` rule `{}`",
|
||||||
|
requested_rule.target.display(),
|
||||||
|
competitor_rule.target.display()
|
||||||
|
)]
|
||||||
|
ScopeConflict {
|
||||||
|
competitor: String,
|
||||||
|
requested_rule: ScopeRule,
|
||||||
|
competitor_rule: ScopeRule,
|
||||||
|
},
|
||||||
#[error("the resolved Worker configuration or persisted history is invalid")]
|
#[error("the resolved Worker configuration or persisted history is invalid")]
|
||||||
WorkerConfiguration,
|
WorkerConfiguration,
|
||||||
#[error("the configured model provider is unavailable")]
|
#[error("the configured model provider is unavailable")]
|
||||||
@@ -509,6 +521,15 @@ fn classify_store_startup_error(error: StandaloneStoreError) -> StandaloneStartu
|
|||||||
|
|
||||||
fn classify_startup_error(error: WorkerBootstrapError) -> StandaloneStartupError {
|
fn classify_startup_error(error: WorkerBootstrapError) -> StandaloneStartupError {
|
||||||
match error {
|
match error {
|
||||||
|
WorkerBootstrapError::Worker(WorkerError::ScopeLock(ScopeLockError::WriteConflict {
|
||||||
|
competitor,
|
||||||
|
rule,
|
||||||
|
competitor_rule,
|
||||||
|
})) => StandaloneStartupError::ScopeConflict {
|
||||||
|
competitor,
|
||||||
|
requested_rule: rule,
|
||||||
|
competitor_rule,
|
||||||
|
},
|
||||||
WorkerBootstrapError::Worker(WorkerError::Provider(_)) => {
|
WorkerBootstrapError::Worker(WorkerError::Provider(_)) => {
|
||||||
StandaloneStartupError::ModelProvider
|
StandaloneStartupError::ModelProvider
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,6 +164,65 @@ async fn in_process_host_runs_text_and_read_tool_then_shuts_down() {
|
|||||||
host.shutdown().await.expect("graceful shutdown");
|
host.shutdown().await.expect("graceful shutdown");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn startup_preserves_occupied_scope_conflict_details() {
|
||||||
|
let temp = tempfile::tempdir().expect("tempdir");
|
||||||
|
let cwd = temp.path().join("project");
|
||||||
|
std::fs::create_dir(&cwd).expect("create project");
|
||||||
|
|
||||||
|
let first_launch = StandaloneLaunchConfig::new(
|
||||||
|
&cwd,
|
||||||
|
temp.path().join("first-state"),
|
||||||
|
manifest::ProfileSelector::Default,
|
||||||
|
"first-worker",
|
||||||
|
)
|
||||||
|
.resolve()
|
||||||
|
.expect("resolve first launch");
|
||||||
|
let first_host =
|
||||||
|
StandaloneHost::start_with_model_client(first_launch, ScriptedClient::new(Vec::new()))
|
||||||
|
.await
|
||||||
|
.expect("start first host");
|
||||||
|
let competitor = first_host.record().storage_key.clone();
|
||||||
|
|
||||||
|
let second_launch = StandaloneLaunchConfig::new(
|
||||||
|
&cwd,
|
||||||
|
temp.path().join("second-state"),
|
||||||
|
manifest::ProfileSelector::Default,
|
||||||
|
"second-worker",
|
||||||
|
)
|
||||||
|
.resolve()
|
||||||
|
.expect("resolve second launch");
|
||||||
|
let error =
|
||||||
|
StandaloneHost::start_with_model_client(second_launch, ScriptedClient::new(Vec::new()))
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.expect("occupied scope rejected");
|
||||||
|
|
||||||
|
first_host.shutdown().await.expect("shutdown first host");
|
||||||
|
|
||||||
|
let canonical_cwd = cwd.canonicalize().expect("canonical cwd");
|
||||||
|
match &error {
|
||||||
|
StandaloneStartupError::ScopeConflict {
|
||||||
|
competitor: actual_competitor,
|
||||||
|
requested_rule,
|
||||||
|
competitor_rule,
|
||||||
|
} => {
|
||||||
|
assert_eq!(actual_competitor, &competitor);
|
||||||
|
assert_eq!(requested_rule.target, canonical_cwd);
|
||||||
|
assert_eq!(competitor_rule.target, canonical_cwd);
|
||||||
|
}
|
||||||
|
other => panic!("expected scope conflict, got {other:?}"),
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
error.to_string(),
|
||||||
|
format!(
|
||||||
|
"requested scope `{}` conflicts with worker allocation `{competitor}` rule `{}`",
|
||||||
|
canonical_cwd.display(),
|
||||||
|
canonical_cwd.display()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn state_store_failure_is_redacted_and_starts_no_controller() {
|
async fn state_store_failure_is_redacted_and_starts_no_controller() {
|
||||||
let temp = tempfile::tempdir().expect("tempdir");
|
let temp = tempfile::tempdir().expect("tempdir");
|
||||||
|
|||||||
Reference in New Issue
Block a user