fix: persist Workdir Runtime failure classifications

This commit is contained in:
2026-08-24 13:59:52 +09:00
parent d9048954a5
commit 18c37f4842
+114 -34
View File
@@ -7693,6 +7693,17 @@ fn registered_workdir_runtime_id(
}) })
} }
fn workdir_runtime_failure_code(error: &RuntimeRegistryError) -> String {
match error {
RuntimeRegistryError::UnknownRuntime(_) | RuntimeRegistryError::UnknownHost(_) => {
"runtime_unavailable".to_string()
}
RuntimeRegistryError::RuntimeOperationFailed { code, .. } => code.clone(),
RuntimeRegistryError::InvalidIdentifier { .. } => "invalid_runtime".to_string(),
RuntimeRegistryError::UnknownWorker { .. } => "runtime_workdir_lookup_failed".to_string(),
}
}
async fn create_workspace_working_directory( async fn create_workspace_working_directory(
api: &WorkspaceApi, api: &WorkspaceApi,
workspace_id: &str, workspace_id: &str,
@@ -7762,9 +7773,16 @@ async fn create_workspace_working_directory(
.clone() .clone()
.or(runtime_projection.default_runtime_id.clone()) .or(runtime_projection.default_runtime_id.clone())
.ok_or_else(|| { .ok_or_else(|| {
Error::InvalidInput( ApiError::with_diagnostics(
"runtime_id was omitted and Workspace configuration has no runtime.default_runtime_id" Error::InvalidInput(
.to_string(), "runtime_id was omitted and Workspace configuration has no runtime.default_runtime_id"
.to_string(),
),
vec![RuntimeDiagnostic {
code: "default_runtime_not_configured".to_string(),
severity: DiagnosticSeverity::Error,
message: "Workspace default Runtime is not configured".to_string(),
}],
) )
})?; })?;
let now = now_registry_timestamp(); let now = now_registry_timestamp();
@@ -7810,10 +7828,17 @@ async fn create_workspace_working_directory(
&operation_id, &operation_id,
&request_fingerprint, &request_fingerprint,
false, false,
Some("resolved Runtime is not registered"), Some("runtime_unavailable"),
&now_registry_timestamp(), &now_registry_timestamp(),
)?; )?;
return Err(Error::UnknownRuntime(reserved.resolved_runtime_id).into()); return Err(ApiError::with_diagnostics(
Error::UnknownRuntime(reserved.resolved_runtime_id),
vec![RuntimeDiagnostic {
code: "runtime_unavailable".to_string(),
severity: DiagnosticSeverity::Error,
message: "Selected Runtime is not available".to_string(),
}],
));
} }
}; };
if runtime.kind == "remote_http" && runtime.status != "connected" { if runtime.kind == "remote_http" && runtime.status != "connected" {
@@ -7822,15 +7847,21 @@ async fn create_workspace_working_directory(
&operation_id, &operation_id,
&request_fingerprint, &request_fingerprint,
false, false,
Some("resolved Runtime is unavailable"), Some("runtime_unavailable"),
&now_registry_timestamp(), &now_registry_timestamp(),
)?; )?;
return Err(Error::RuntimeOperationFailed { return Err(ApiError::with_diagnostics(
runtime_id: reserved.resolved_runtime_id, Error::RuntimeOperationFailed {
code: "runtime_unavailable".to_string(), runtime_id: reserved.resolved_runtime_id,
message: "Selected Runtime is not connected".to_string(), code: "runtime_unavailable".to_string(),
} message: "Selected Runtime is not connected".to_string(),
.into()); },
vec![RuntimeDiagnostic {
code: "runtime_unavailable".to_string(),
severity: DiagnosticSeverity::Error,
message: "Selected Runtime is not available".to_string(),
}],
));
} }
if !runtime.capabilities.supports_worktrees { if !runtime.capabilities.supports_worktrees {
@@ -7839,25 +7870,49 @@ async fn create_workspace_working_directory(
&operation_id, &operation_id,
&request_fingerprint, &request_fingerprint,
false, false,
Some("resolved Runtime does not support Workdir creation"), Some("runtime_workdir_unsupported"),
&now_registry_timestamp(), &now_registry_timestamp(),
)?; )?;
return Err(Error::RuntimeOperationFailed { return Err(ApiError::with_diagnostics(
runtime_id: reserved.resolved_runtime_id, Error::RuntimeOperationFailed {
code: "workdir_create_unsupported".to_string(), runtime_id: reserved.resolved_runtime_id,
message: "Selected Runtime does not support Workdir creation".to_string(), code: "runtime_workdir_unsupported".to_string(),
} message: "Selected Runtime does not support Workdir creation".to_string(),
.into()); },
vec![RuntimeDiagnostic {
code: "runtime_workdir_unsupported".to_string(),
severity: DiagnosticSeverity::Error,
message: "Selected Runtime does not support Workdir creation".to_string(),
}],
));
} }
working_directory_request.backend_workdir_id = Some(reserved.working_directory_id.clone()); working_directory_request.backend_workdir_id = Some(reserved.working_directory_id.clone());
let existing = api let existing = match api.runtime.working_directory(
.runtime &reserved.resolved_runtime_id,
.working_directory( &reserved.working_directory_id,
&reserved.resolved_runtime_id, ) {
&reserved.working_directory_id, Ok(existing) => existing,
) Err(error) => {
.map_err(|err| err.into_error())?; let failure_code = workdir_runtime_failure_code(&error);
api.config_store.finish_workdir_create_operation(
workspace_id,
&operation_id,
&request_fingerprint,
false,
Some(&failure_code),
&now_registry_timestamp(),
)?;
return Err(ApiError::with_diagnostics(
error.into_error(),
vec![RuntimeDiagnostic {
code: failure_code,
severity: DiagnosticSeverity::Error,
message: "Selected Runtime could not inspect the reserved Workdir".to_string(),
}],
));
}
};
let result = if existing.working_directory.is_some() { let result = if existing.working_directory.is_some() {
existing existing
} else { } else {
@@ -7867,7 +7922,7 @@ async fn create_workspace_working_directory(
{ {
Ok(result) => result, Ok(result) => result,
Err(error) => { Err(error) => {
let error = error.into_error(); let failure_code = workdir_runtime_failure_code(&error);
let _ = api let _ = api
.store .store
.delete_workdir_registry(workspace_id, &reserved.working_directory_id); .delete_workdir_registry(workspace_id, &reserved.working_directory_id);
@@ -7876,10 +7931,17 @@ async fn create_workspace_working_directory(
&operation_id, &operation_id,
&request_fingerprint, &request_fingerprint,
false, false,
Some("runtime rejected Workdir creation"), Some(&failure_code),
&now_registry_timestamp(), &now_registry_timestamp(),
)?; )?;
return Err(error.into()); return Err(ApiError::with_diagnostics(
error.into_error(),
vec![RuntimeDiagnostic {
code: failure_code,
severity: DiagnosticSeverity::Error,
message: "Selected Runtime rejected Workdir creation".to_string(),
}],
));
} }
} }
}; };
@@ -7892,16 +7954,25 @@ async fn create_workspace_working_directory(
&operation_id, &operation_id,
&request_fingerprint, &request_fingerprint,
false, false,
Some("runtime did not create working directory"), Some("runtime_workdir_create_failed"),
&now_registry_timestamp(), &now_registry_timestamp(),
)?; )?;
let mut diagnostics = result.diagnostics;
diagnostics.insert(
0,
RuntimeDiagnostic {
code: "runtime_workdir_create_failed".to_string(),
severity: DiagnosticSeverity::Error,
message: "Runtime did not create the reserved Workdir".to_string(),
},
);
return Err(ApiError::with_diagnostics( return Err(ApiError::with_diagnostics(
Error::RuntimeOperationFailed { Error::RuntimeOperationFailed {
runtime_id: reserved.resolved_runtime_id, runtime_id: reserved.resolved_runtime_id,
code: "workspace_working_directory_create_failed".to_string(), code: "runtime_workdir_create_failed".to_string(),
message: "Runtime did not create working directory".to_string(), message: "Runtime did not create working directory".to_string(),
}, },
result.diagnostics, diagnostics,
)); ));
}; };
let record = workdir_record_from_summary( let record = workdir_record_from_summary(
@@ -18937,6 +19008,10 @@ mod tests {
) )
.await; .await;
assert_eq!(response["error"], "Bad Request"); assert_eq!(response["error"], "Bad Request");
assert_eq!(
response["diagnostics"][0]["code"],
"default_runtime_not_configured"
);
let projected = serde_json::to_string(&response).unwrap(); let projected = serde_json::to_string(&response).unwrap();
assert!(!projected.contains(dir.path().to_string_lossy().as_ref())); assert!(!projected.contains(dir.path().to_string_lossy().as_ref()));
} }
@@ -18963,6 +19038,7 @@ mod tests {
) )
.await; .await;
assert_eq!(response["error"], "Not Found"); assert_eq!(response["error"], "Not Found");
assert_eq!(response["diagnostics"][0]["code"], "runtime_unavailable");
let operation = api let operation = api
.config_store .config_store
.load_workdir_create_operation(TEST_WORKSPACE_ID, operation_id) .load_workdir_create_operation(TEST_WORKSPACE_ID, operation_id)
@@ -18974,6 +19050,7 @@ mod tests {
); );
assert_eq!(operation.resolved_runtime_id, "missing-runtime"); assert_eq!(operation.resolved_runtime_id, "missing-runtime");
assert_eq!(operation.state, "failed"); assert_eq!(operation.state, "failed");
assert_eq!(operation.failure.as_deref(), Some("runtime_unavailable"));
assert!( assert!(
api.store api.store
.get_workdir_registry(TEST_WORKSPACE_ID, &operation.working_directory_id) .get_workdir_registry(TEST_WORKSPACE_ID, &operation.working_directory_id)
@@ -19004,7 +19081,10 @@ mod tests {
) )
.await; .await;
assert_eq!(response["error"], "Bad Request"); assert_eq!(response["error"], "Bad Request");
assert_eq!(
response["diagnostics"][0]["code"],
"runtime_workdir_unsupported"
);
set_test_default_runtime(&api, "not-a-registered-runtime"); set_test_default_runtime(&api, "not-a-registered-runtime");
request_json( request_json(
app, app,
@@ -19028,7 +19108,7 @@ mod tests {
assert_eq!(operation.state, "failed"); assert_eq!(operation.state, "failed");
assert_eq!( assert_eq!(
operation.failure.as_deref(), operation.failure.as_deref(),
Some("resolved Runtime does not support Workdir creation") Some("runtime_workdir_unsupported")
); );
assert_eq!(operation.config_revision, 2); assert_eq!(operation.config_revision, 2);
assert!(!operation.config_projection_digest.is_empty()); assert!(!operation.config_projection_digest.is_empty());