fix: restore production orchestrator spawn
This commit is contained in:
@@ -3602,6 +3602,59 @@ fn embedded_workdir_unsupported_diagnostic() -> RuntimeDiagnostic {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sanitize_embedded_execution_message(
|
||||||
|
message: &str,
|
||||||
|
operation: &impl std::fmt::Debug,
|
||||||
|
outcome: &impl std::fmt::Debug,
|
||||||
|
) -> String {
|
||||||
|
let summary =
|
||||||
|
format!("Embedded Worker execution backend rejected {operation:?} with {outcome:?}");
|
||||||
|
let mut redact_next = false;
|
||||||
|
let detail = message
|
||||||
|
.split_whitespace()
|
||||||
|
.map(|part| {
|
||||||
|
if redact_next {
|
||||||
|
redact_next = false;
|
||||||
|
return "[redacted]";
|
||||||
|
}
|
||||||
|
let lowercase = part.to_ascii_lowercase();
|
||||||
|
let label =
|
||||||
|
lowercase.trim_matches(|character: char| !character.is_ascii_alphanumeric());
|
||||||
|
if matches!(
|
||||||
|
label,
|
||||||
|
"bearer" | "credential" | "key" | "password" | "secret" | "session" | "token"
|
||||||
|
) {
|
||||||
|
redact_next = true;
|
||||||
|
}
|
||||||
|
if part.contains('/')
|
||||||
|
|| part.contains('\\')
|
||||||
|
|| lowercase.contains("credential=")
|
||||||
|
|| lowercase.contains("key=")
|
||||||
|
|| lowercase.contains("password=")
|
||||||
|
|| lowercase.contains("secret=")
|
||||||
|
|| lowercase.contains("session=")
|
||||||
|
|| lowercase.contains("session_id=")
|
||||||
|
|| lowercase.contains("token=")
|
||||||
|
{
|
||||||
|
"[redacted]"
|
||||||
|
} else {
|
||||||
|
part
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ");
|
||||||
|
let detail = detail.trim();
|
||||||
|
if detail.is_empty() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
let truncated = detail.chars().count() > 512;
|
||||||
|
let mut detail = detail.chars().take(512).collect::<String>();
|
||||||
|
if truncated {
|
||||||
|
detail.push('…');
|
||||||
|
}
|
||||||
|
format!("{summary}: {detail}")
|
||||||
|
}
|
||||||
|
|
||||||
fn embedded_runtime_diagnostic(error: &EmbeddedRuntimeError) -> RuntimeDiagnostic {
|
fn embedded_runtime_diagnostic(error: &EmbeddedRuntimeError) -> RuntimeDiagnostic {
|
||||||
match error {
|
match error {
|
||||||
EmbeddedRuntimeError::RuntimeStopped => diagnostic(
|
EmbeddedRuntimeError::RuntimeStopped => diagnostic(
|
||||||
@@ -3621,11 +3674,14 @@ fn embedded_runtime_diagnostic(error: &EmbeddedRuntimeError) -> RuntimeDiagnosti
|
|||||||
"Embedded Worker has no execution backend attached".to_string(),
|
"Embedded Worker has no execution backend attached".to_string(),
|
||||||
),
|
),
|
||||||
EmbeddedRuntimeError::WorkerExecutionRejected {
|
EmbeddedRuntimeError::WorkerExecutionRejected {
|
||||||
operation, outcome, ..
|
operation,
|
||||||
|
outcome,
|
||||||
|
message,
|
||||||
|
..
|
||||||
} => diagnostic(
|
} => diagnostic(
|
||||||
"embedded_worker_execution_rejected",
|
"embedded_worker_execution_rejected",
|
||||||
DiagnosticSeverity::Warning,
|
DiagnosticSeverity::Warning,
|
||||||
format!("Embedded Worker execution backend rejected {operation:?} with {outcome:?}"),
|
sanitize_embedded_execution_message(message, operation, outcome),
|
||||||
),
|
),
|
||||||
EmbeddedRuntimeError::LimitTooLarge { requested, max } => diagnostic(
|
EmbeddedRuntimeError::LimitTooLarge { requested, max } => diagnostic(
|
||||||
"embedded_runtime_limit_too_large",
|
"embedded_runtime_limit_too_large",
|
||||||
@@ -4263,7 +4319,7 @@ mod tests {
|
|||||||
worker_runtime::execution::WorkerExecutionSpawnResult::Errored(
|
worker_runtime::execution::WorkerExecutionSpawnResult::Errored(
|
||||||
worker_runtime::execution::WorkerExecutionResult::errored(
|
worker_runtime::execution::WorkerExecutionResult::errored(
|
||||||
worker_runtime::execution::WorkerExecutionOperation::Spawn,
|
worker_runtime::execution::WorkerExecutionOperation::Spawn,
|
||||||
"provider setup failed at /tmp/secret-provider-config",
|
"provider setup failed at /tmp/secret-provider-config token=secret-value session_id=session-42",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -4643,7 +4699,12 @@ mod tests {
|
|||||||
assert!(spawned.acceptance_evidence.is_empty());
|
assert!(spawned.acceptance_evidence.is_empty());
|
||||||
assert!(spawned.diagnostics.iter().any(|diagnostic| {
|
assert!(spawned.diagnostics.iter().any(|diagnostic| {
|
||||||
diagnostic.code == "embedded_worker_execution_rejected"
|
diagnostic.code == "embedded_worker_execution_rejected"
|
||||||
|
&& diagnostic
|
||||||
|
.message
|
||||||
|
.contains("provider setup failed at [redacted]")
|
||||||
&& !diagnostic.message.contains("/tmp/secret-provider-config")
|
&& !diagnostic.message.contains("/tmp/secret-provider-config")
|
||||||
|
&& !diagnostic.message.contains("secret-value")
|
||||||
|
&& !diagnostic.message.contains("session-42")
|
||||||
}));
|
}));
|
||||||
assert!(spawned.worker.is_none());
|
assert!(spawned.worker.is_none());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10421,6 +10421,115 @@ mod tests {
|
|||||||
assert!(matches!(error, Error::WorkerSourceIdentity(_)));
|
assert!(matches!(error, Error::WorkerSourceIdentity(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn production_profile_backend_launches_and_restores_workspace_orchestrator() {
|
||||||
|
let workspace = tempfile::tempdir().unwrap();
|
||||||
|
init_clean_git_workspace(workspace.path());
|
||||||
|
let config = test_server_config(workspace.path());
|
||||||
|
let store = SqliteWorkspaceStore::open(config.database_path.clone()).unwrap();
|
||||||
|
let api = WorkspaceApi::new(config, Arc::new(store)).await.unwrap();
|
||||||
|
let workspace_id = api.config.workspace_id.clone();
|
||||||
|
|
||||||
|
let result = scoped_start_workspace_orchestrator(
|
||||||
|
State(api.clone()),
|
||||||
|
AxumPath(ScopedWorkspacePath {
|
||||||
|
workspace_id: workspace_id.clone(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let Json(started) = result.unwrap_or_else(|error| {
|
||||||
|
panic!(
|
||||||
|
"production Workspace Orchestrator launch failed: error={:?}, diagnostics={:?}",
|
||||||
|
error.error, error.diagnostics
|
||||||
|
)
|
||||||
|
});
|
||||||
|
assert_eq!(started.disposition, "created");
|
||||||
|
assert!(started.online);
|
||||||
|
let worker = started
|
||||||
|
.worker
|
||||||
|
.expect("production Workspace Orchestrator Worker")
|
||||||
|
.worker;
|
||||||
|
|
||||||
|
let stopped = api
|
||||||
|
.runtime
|
||||||
|
.stop_worker(
|
||||||
|
&worker,
|
||||||
|
WorkerLifecycleRequest {
|
||||||
|
reason: Some("production Orchestrator restore regression test".to_string()),
|
||||||
|
ticket_assignment: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(stopped.state, WorkerOperationState::Accepted);
|
||||||
|
let Json(restored) = scoped_start_workspace_orchestrator(
|
||||||
|
State(api),
|
||||||
|
AxumPath(ScopedWorkspacePath { workspace_id }),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(restored.disposition, "restored");
|
||||||
|
assert!(restored.online);
|
||||||
|
assert_eq!(
|
||||||
|
restored.worker.expect("restored Orchestrator").worker,
|
||||||
|
worker
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn rejected_orchestrator_spawn_stays_offline_and_can_be_retried() {
|
||||||
|
let workspace = tempfile::tempdir().unwrap();
|
||||||
|
init_clean_git_workspace(workspace.path());
|
||||||
|
let config = test_server_config(workspace.path());
|
||||||
|
let store = SqliteWorkspaceStore::open(config.database_path.clone()).unwrap();
|
||||||
|
let api = WorkspaceApi::new_with_execution_backend(
|
||||||
|
config,
|
||||||
|
Arc::new(store),
|
||||||
|
Arc::new(DeterministicExecutionBackend::fail_first_spawn(
|
||||||
|
"provider setup rejected safe-root /tmp/private token=private-token session_id=private-session",
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let workspace_id = api.config.workspace_id.clone();
|
||||||
|
|
||||||
|
let error = scoped_start_workspace_orchestrator(
|
||||||
|
State(api.clone()),
|
||||||
|
AxumPath(ScopedWorkspacePath {
|
||||||
|
workspace_id: workspace_id.clone(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap_err();
|
||||||
|
assert!(matches!(
|
||||||
|
error.error,
|
||||||
|
Error::RuntimeOperationFailed {
|
||||||
|
ref code,
|
||||||
|
..
|
||||||
|
} if code == "workspace_orchestrator_spawn_rejected"
|
||||||
|
));
|
||||||
|
assert!(error.diagnostics.iter().any(|diagnostic| {
|
||||||
|
diagnostic.code == "embedded_worker_execution_rejected"
|
||||||
|
&& diagnostic
|
||||||
|
.message
|
||||||
|
.contains("provider setup rejected safe-root")
|
||||||
|
&& !diagnostic.message.contains("/tmp/private")
|
||||||
|
&& !diagnostic.message.contains("private-token")
|
||||||
|
&& !diagnostic.message.contains("private-session")
|
||||||
|
}));
|
||||||
|
assert!(find_workspace_orchestrator(&api).is_none());
|
||||||
|
assert!(!workspace_orchestrator_response(&api, "failed").online);
|
||||||
|
|
||||||
|
let Json(retried) = scoped_start_workspace_orchestrator(
|
||||||
|
State(api),
|
||||||
|
AxumPath(ScopedWorkspacePath { workspace_id }),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(retried.disposition, "created");
|
||||||
|
assert!(retried.online);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn explicit_orchestrator_launch_marks_only_the_dedicated_worker() {
|
async fn explicit_orchestrator_launch_marks_only_the_dedicated_worker() {
|
||||||
let workspace = tempfile::tempdir().unwrap();
|
let workspace = tempfile::tempdir().unwrap();
|
||||||
@@ -11036,6 +11145,7 @@ mod tests {
|
|||||||
>,
|
>,
|
||||||
>,
|
>,
|
||||||
materializer: worker_runtime::working_directory::LocalGitWorktreeMaterializer,
|
materializer: worker_runtime::working_directory::LocalGitWorktreeMaterializer,
|
||||||
|
spawn_failure: std::sync::Mutex<Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DeterministicExecutionBackend {
|
impl Default for DeterministicExecutionBackend {
|
||||||
@@ -11053,10 +11163,19 @@ mod tests {
|
|||||||
materializer: worker_runtime::working_directory::LocalGitWorktreeMaterializer::new(
|
materializer: worker_runtime::working_directory::LocalGitWorktreeMaterializer::new(
|
||||||
std::env::temp_dir().join(unique),
|
std::env::temp_dir().join(unique),
|
||||||
),
|
),
|
||||||
|
spawn_failure: std::sync::Mutex::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DeterministicExecutionBackend {
|
||||||
|
fn fail_first_spawn(message: impl Into<String>) -> Self {
|
||||||
|
let backend = Self::default();
|
||||||
|
*backend.spawn_failure.lock().unwrap() = Some(message.into());
|
||||||
|
backend
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl worker_runtime::execution::WorkerExecutionBackend for DeterministicExecutionBackend {
|
impl worker_runtime::execution::WorkerExecutionBackend for DeterministicExecutionBackend {
|
||||||
fn backend_id(&self) -> &str {
|
fn backend_id(&self) -> &str {
|
||||||
"deterministic-workspace-server-test"
|
"deterministic-workspace-server-test"
|
||||||
@@ -11104,6 +11223,14 @@ mod tests {
|
|||||||
&self,
|
&self,
|
||||||
request: worker_runtime::execution::WorkerExecutionSpawnRequest,
|
request: worker_runtime::execution::WorkerExecutionSpawnRequest,
|
||||||
) -> worker_runtime::execution::WorkerExecutionSpawnResult {
|
) -> worker_runtime::execution::WorkerExecutionSpawnResult {
|
||||||
|
if let Some(message) = self.spawn_failure.lock().unwrap().take() {
|
||||||
|
return worker_runtime::execution::WorkerExecutionSpawnResult::Errored(
|
||||||
|
worker_runtime::execution::WorkerExecutionResult::errored(
|
||||||
|
worker_runtime::execution::WorkerExecutionOperation::Spawn,
|
||||||
|
message,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
let working_directory = match request.request.working_directory.as_ref() {
|
let working_directory = match request.request.working_directory.as_ref() {
|
||||||
Some(claim) => match self.materializer.bind_working_directory(
|
Some(claim) => match self.materializer.bind_working_directory(
|
||||||
&claim.working_directory_id,
|
&claim.working_directory_id,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import "./base.dcdl" // {
|
|||||||
task = { enabled = true; };
|
task = { enabled = true; };
|
||||||
memory = { enabled = true; };
|
memory = { enabled = true; };
|
||||||
web = { enabled = true; };
|
web = { enabled = true; };
|
||||||
sub_worker = { enabled = true; };
|
sub_worker = { enabled = false; };
|
||||||
worker = { enabled = true; };
|
worker = { enabled = true; };
|
||||||
manage_workdir = { enabled = true; };
|
manage_workdir = { enabled = true; };
|
||||||
ticket = { enabled = true; thread = true; orchestration_control = true; };
|
ticket = { enabled = true; thread = true; orchestration_control = true; };
|
||||||
|
|||||||
Reference in New Issue
Block a user