fix: retain structured Workdir refusal diagnostics across provider boundaries

This commit is contained in:
2026-10-09 22:23:22 +09:00
parent e8828cd1b5
commit f9d70e2ae2
28 changed files with 2053 additions and 198 deletions
+18 -9
View File
@@ -191,15 +191,24 @@ impl Provider {
};
let observation = match selected.session.checkout_observe(target.clone()).await {
Ok(o) => o,
Err(
workdir::WorkdirError::Denied(_)
| workdir::WorkdirError::OutOfScope(_)
| workdir::WorkdirError::NotFound(_)
| workdir::WorkdirError::SessionClosed
| workdir::WorkdirError::UnsupportedOperation(_)
| workdir::WorkdirError::Unsupported(_),
) => return Ok(None),
Err(_) => {
Err(failure) => {
// Internal diagnostic enrichment is transparent to WIP's existing
// unavailable/refusal classification, including remote providers.
let mut classification = &failure;
while let workdir::WorkdirError::DenialContext { source, .. } = classification {
classification = source.as_ref();
}
if matches!(
classification,
workdir::WorkdirError::Denied(_)
| workdir::WorkdirError::OutOfScope(_)
| workdir::WorkdirError::NotFound(_)
| workdir::WorkdirError::SessionClosed
| workdir::WorkdirError::UnsupportedOperation(_)
| workdir::WorkdirError::Unsupported(_)
) {
return Ok(None);
}
return Err(error(
ProtocolErrorCode::Internal,
"checkout observation unavailable",
+40
View File
@@ -36,6 +36,7 @@ struct ProviderState {
requests: Mutex<Vec<(String, String)>>,
operations: Mutex<Vec<WorkdirSessionOperation>>,
write_fault: Mutex<Option<WriteResponseFault>>,
observe_refusal: Mutex<bool>,
}
impl ProviderState {
@@ -49,6 +50,7 @@ impl ProviderState {
return Err((
StatusCode::UNAUTHORIZED,
AxumJson(WorkdirTransportError {
denial_reason: None,
code: WorkdirTransportErrorCode::Denied,
message: "provider authorization denied".into(),
}),
@@ -140,6 +142,16 @@ async fn operate(
.lock()
.unwrap()
.push(request.operation.clone());
if matches!(
&request.operation,
WorkdirSessionOperation::CheckoutObserve(_)
) && *state.observe_refusal.lock().unwrap()
{
return provider_error(WorkdirError::DenialContext {
reason: workdir::WorkdirDenialReason::OsPermissionDenied,
source: Box::new(WorkdirError::OutOfScope("/private/provider/path".into())),
});
}
let result =
dispatch_workdir_session_operation(state.session.as_ref(), request.operation).await;
match result {
@@ -225,6 +237,7 @@ impl HttpFixture {
requests: Mutex::new(Vec::new()),
operations: Mutex::new(Vec::new()),
write_fault: Mutex::new(None),
observe_refusal: Mutex::new(false),
});
let app = Router::new()
.route(
@@ -755,3 +768,30 @@ async fn remote_checkout_list_pages_and_direct_inspect_read_use_provider_without
.any(|op| matches!(op, WorkdirSessionOperation::List(_)))
);
}
#[tokio::test]
async fn remote_checkout_diagnostic_context_keeps_wip_refusal_classification_without_retry() {
let fixture = HttpFixture::new(WorkdirSessionCapabilities::READ_ONLY).await;
*fixture.state.observe_refusal.lock().unwrap() = true;
let remote = fixture.open(TOKEN).await.unwrap();
let (runtime, _) = native_runtime(Arc::new(remote));
let failure = runtime
.inspect("/checkouts/main/src/deep/a.txt".into(), true)
.await
.unwrap_err();
let text = failure.to_string();
assert!(
!text.contains("checkout observation unavailable"),
"refusal became an internal error: {text}"
);
assert!(!text.contains("/private/provider/path"));
assert!(
text.contains("protocol failure (NotFound)"),
"expected unavailable Object: {text}"
);
assert_eq!(
fixture.state.operations.lock().unwrap().len(),
1,
"refusal was retried"
);
}
@@ -1236,6 +1236,7 @@ mod tests {
WorkspaceResponse {
status,
body: serde_json::to_string(&workdir::http::WorkdirTransportError {
denial_reason: None,
code,
message: message.to_string(),
})