fix: preserve Workdir provider rejection codes

This commit is contained in:
2026-08-24 14:30:34 +09:00
parent 29f450b962
commit e4e045d059
+153 -13
View File
@@ -7704,6 +7704,42 @@ fn workdir_runtime_failure_code(error: &RuntimeRegistryError) -> String {
}
}
fn workdir_rejection_failure_code(diagnostics: &[RuntimeDiagnostic]) -> String {
diagnostics
.iter()
.find(|diagnostic| {
diagnostic.severity == DiagnosticSeverity::Error
&& !diagnostic.code.is_empty()
&& diagnostic.code.len() <= 128
&& diagnostic
.code
.bytes()
.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_')
})
.map(|diagnostic| diagnostic.code.clone())
.unwrap_or_else(|| "runtime_workdir_create_failed".to_string())
}
fn finish_rejected_workdir_create_operation(
store: &crate::SqliteWorkspaceStore,
workspace_id: &str,
operation_id: &str,
request_fingerprint: &str,
diagnostics: &[RuntimeDiagnostic],
updated_at: &str,
) -> Result<String> {
let failure_code = workdir_rejection_failure_code(diagnostics);
store.finish_workdir_create_operation(
workspace_id,
operation_id,
request_fingerprint,
false,
Some(&failure_code),
updated_at,
)?;
Ok(failure_code)
}
async fn create_workspace_working_directory(
api: &WorkspaceApi,
workspace_id: &str,
@@ -7949,27 +7985,31 @@ async fn create_workspace_working_directory(
let _ = api
.store
.delete_workdir_registry(workspace_id, &reserved.working_directory_id);
api.config_store.finish_workdir_create_operation(
let mut diagnostics = result.diagnostics;
let failure_code = finish_rejected_workdir_create_operation(
&api.config_store,
workspace_id,
&operation_id,
&request_fingerprint,
false,
Some("runtime_workdir_create_failed"),
&diagnostics,
&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(),
},
);
if !diagnostics.iter().any(|diagnostic| {
diagnostic.severity == DiagnosticSeverity::Error && diagnostic.code == failure_code
}) {
diagnostics.insert(
0,
RuntimeDiagnostic {
code: failure_code.clone(),
severity: DiagnosticSeverity::Error,
message: "Runtime did not create the reserved Workdir".to_string(),
},
);
}
return Err(ApiError::with_diagnostics(
Error::RuntimeOperationFailed {
runtime_id: reserved.resolved_runtime_id,
code: "runtime_workdir_create_failed".to_string(),
code: failure_code,
message: "Runtime did not create working directory".to_string(),
},
diagnostics,
@@ -13582,6 +13622,12 @@ impl IntoResponse for ApiError {
{
StatusCode::BAD_REQUEST
}
Error::RuntimeOperationFailed { code, .. }
if code == "runtime_capacity_unavailable"
|| code == "runtime_workdir_capacity_unavailable" =>
{
StatusCode::SERVICE_UNAVAILABLE
}
Error::RuntimeOperationFailed { .. } => StatusCode::BAD_GATEWAY,
_ => StatusCode::INTERNAL_SERVER_ERROR,
};
@@ -18990,6 +19036,100 @@ mod tests {
(runtime, worker.worker_ref)
}
#[test]
fn provider_rejection_classification_prefers_stable_error_diagnostic() {
let diagnostics = vec![
RuntimeDiagnostic {
code: "runtime_capacity_warning".to_string(),
severity: DiagnosticSeverity::Warning,
message: "capacity is low".to_string(),
},
RuntimeDiagnostic {
code: "runtime_capacity_unavailable".to_string(),
severity: DiagnosticSeverity::Error,
message: "capacity is exhausted".to_string(),
},
];
assert_eq!(
workdir_rejection_failure_code(&diagnostics),
"runtime_capacity_unavailable"
);
assert_eq!(
workdir_rejection_failure_code(&[]),
"runtime_workdir_create_failed"
);
}
#[tokio::test]
async fn provider_rejection_persists_stable_failure_classification() {
let dir = tempfile::tempdir().unwrap();
init_clean_git_workspace(dir.path());
let api = test_api(dir.path()).await;
let operation_id = "provider-rejection-classification";
let request_fingerprint = crate::workdir_create_operations::request_fingerprint(
TEST_REPOSITORY_ID,
Some("HEAD"),
Some(EMBEDDED_WORKER_RUNTIME_ID),
);
api.config_store
.reserve_workdir_create_operation(&WorkdirCreateOperationRecord {
workspace_id: TEST_WORKSPACE_ID.to_string(),
operation_id: operation_id.to_string(),
request_fingerprint: request_fingerprint.clone(),
repository_id: TEST_REPOSITORY_ID.to_string(),
selector: Some("HEAD".to_string()),
requested_runtime_id: Some(EMBEDDED_WORKER_RUNTIME_ID.to_string()),
resolved_runtime_id: EMBEDDED_WORKER_RUNTIME_ID.to_string(),
config_revision: 1,
config_projection_digest: "sha256:test".to_string(),
working_directory_id: "workdir-provider-rejection".to_string(),
state: "pending".to_string(),
failure: None,
created_at: now_registry_timestamp(),
updated_at: now_registry_timestamp(),
})
.unwrap();
let diagnostics = vec![RuntimeDiagnostic {
code: "runtime_capacity_unavailable".to_string(),
severity: DiagnosticSeverity::Error,
message: "capacity is exhausted".to_string(),
}];
assert_eq!(
finish_rejected_workdir_create_operation(
&api.config_store,
TEST_WORKSPACE_ID,
operation_id,
&request_fingerprint,
&diagnostics,
&now_registry_timestamp(),
)
.unwrap(),
"runtime_capacity_unavailable"
);
let operation = api
.config_store
.load_workdir_create_operation(TEST_WORKSPACE_ID, operation_id)
.unwrap()
.unwrap();
assert_eq!(operation.state, "failed");
assert_eq!(
operation.failure.as_deref(),
Some("runtime_capacity_unavailable")
);
}
#[test]
fn runtime_capacity_unavailable_maps_to_service_unavailable() {
let response = ApiError::from(Error::RuntimeOperationFailed {
runtime_id: "runtime".to_string(),
code: "runtime_capacity_unavailable".to_string(),
message: "Runtime capacity is exhausted".to_string(),
})
.into_response();
assert_eq!(response.status(), StatusCode::SERVICE_UNAVAILABLE);
}
#[test]
fn default_runtime_not_configured_operation_error_maps_to_bad_request() {
let response = ApiError::from(Error::RuntimeOperationFailed {