fix(worker): reconcile invalid Drive completions and default pages

Accept the Backend shared default page maxima (list 200/search 128), not an unsent 100 limit. Classify semantically invalid completions before receipt reconciliation; query the original identity once and accept only validated committed results, never resend or upgrade revisions.

Regressions fail on 61afdb77 and pass after the fix: real API 201-entry default Tools/native WIP pagination; mismatched completion ID/invalid entry metadata recovery and unresolved/invalid receipt matrices. Root cargo check; Worker 920 unit +110 integration; Server 709 lib +11 binary; Drive unit18/API18; fmt and diff checks passed.
This commit is contained in:
2026-10-09 15:36:06 +09:00
parent 61afdb7773
commit e94af40dab
4 changed files with 251 additions and 21 deletions
@@ -172,7 +172,12 @@ impl DriveBackend {
},
)
.await?;
self.validate_page(&result, limit.unwrap_or(100).min(DRIVE_PAGE_MAX_LIMIT))?;
self.validate_page(
&result,
limit
.unwrap_or(DRIVE_PAGE_MAX_LIMIT)
.min(DRIVE_PAGE_MAX_LIMIT),
)?;
if result
.entries
.iter()
@@ -186,7 +191,10 @@ impl DriveBackend {
let result: DriveListResponse = self.get("/search", &query).await?;
self.validate_page(
&result,
query.limit.unwrap_or(100).min(DRIVE_SEARCH_MAX_LIMIT),
query
.limit
.unwrap_or(DRIVE_SEARCH_MAX_LIMIT)
.min(DRIVE_SEARCH_MAX_LIMIT),
)?;
Ok(result)
}
@@ -276,25 +284,37 @@ impl DriveBackend {
id: &str,
result: Result<DriveMutationResponse, DriveError>,
) -> Result<DriveMutationResponse, DriveError> {
let response = match result {
// A parseable success can still be an invalid completion. Classify it
// before reconciliation so it also gets one original-request inquiry.
let result = result.and_then(|response| {
self.validate_completion(id, &response)
.map_err(|_| DriveError::OutcomeUnknown(id.into()))?;
Ok(response)
});
match result {
Err(DriveError::OutcomeUnknown(_)) => match self.status(id).await {
Ok(DriveRequestStatusResponse {
state: DriveRequestState::Committed,
response: Some(response),
..
}) => response,
_ => return Err(DriveError::OutcomeUnknown(id.into())),
}) => Ok(response), // status validates both IDs and entry metadata
_ => Err(DriveError::OutcomeUnknown(id.into())),
},
other => other?,
};
other => other,
}
}
fn validate_completion(
&self,
id: &str,
response: &DriveMutationResponse,
) -> Result<(), DriveError> {
if response.request_id != id {
return Err(DriveError::OutcomeUnknown(id.into()));
return Err(DriveError::Unavailable);
}
if let Some(entry) = &response.entry {
self.validate_entry(entry)
.map_err(|_| DriveError::OutcomeUnknown(id.into()))?;
self.validate_entry(entry)?;
}
Ok(response)
Ok(())
}
pub async fn mutate(
&self,
@@ -371,12 +391,7 @@ impl DriveBackend {
return Err(DriveError::Unavailable);
}
if let Some(result) = &response.response {
if result.request_id != request_id {
return Err(DriveError::Unavailable);
}
if let Some(entry) = &result.entry {
self.validate_entry(entry)?;
}
self.validate_completion(request_id, result)?;
}
Ok(response)
}
@@ -294,6 +294,143 @@ async fn uncommitted_receipt_snapshot_does_not_turn_unknown_into_failure_or_retr
assert_eq!(client.requests.lock().unwrap().len(), 2);
}
fn invalid_completions(id: &str) -> Vec<DriveMutationResponse> {
let mut invalid_entry = entry("2", "2");
invalid_entry.revision = "not-a-revision".into();
vec![
DriveMutationResponse {
request_id: "another-request".into(),
entry: Some(entry("2", "2")),
},
DriveMutationResponse {
request_id: id.into(),
entry: Some(invalid_entry),
},
]
}
#[tokio::test]
async fn invalid_mutation_completion_recovers_from_original_receipt_without_resending() {
let context = ToolExecutionContext::new("write", "batch", 0);
let client = Client::new(vec![]);
let feature = feature(client.clone());
let id = feature.request_id(&context);
for completion in invalid_completions(&id) {
let committed = DriveMutationResponse {
request_id: id.clone(),
entry: Some(entry("2", "2")),
};
client.requests.lock().unwrap().clear();
client.responses.lock().unwrap().extend([
Ok(response(completion)),
Ok(response(DriveRequestStatusResponse {
request_id: id.clone(),
state: DriveRequestState::Committed,
response: Some(committed.clone()),
})),
]);
feature.observe(entry("2", "1")).unwrap();
let output = feature
.execute(
"DriveWrite",
&json!({"entry":entry("2", "1").entry,"content":"new"}).to_string(),
&context,
None,
)
.await
.unwrap();
assert_eq!(output.value["request_id"], id);
assert_eq!(output.value["entry"]["metadata"]["revision"], "2");
let requests = client.requests.lock().unwrap();
assert_eq!(requests.len(), 2);
assert_eq!(
requests[0].method,
crate::worker::WorkspaceRequestMethod::Post
);
assert_eq!(
requests[1].method,
crate::worker::WorkspaceRequestMethod::Get
);
assert!(requests[1].path.ends_with(&format!("/requests/{id}")));
let body: Value = serde_json::from_str(requests[0].body.as_ref().unwrap()).unwrap();
assert_eq!(body["request_id"], id);
assert_eq!(body["mutation"]["expected_revision"], "1");
}
}
#[tokio::test]
async fn invalid_completion_and_unresolved_or_invalid_receipt_stay_unknown_after_one_query() {
let context = ToolExecutionContext::new("write", "batch", 0);
let client = Client::new(vec![]);
let feature = feature(client.clone());
let id = feature.request_id(&context);
let committed = DriveMutationResponse {
request_id: id.clone(),
entry: Some(entry("2", "2")),
};
let mut receipts = vec![
response(DriveRequestStatusResponse {
request_id: id.clone(),
state: DriveRequestState::Uncommitted,
response: None,
}),
response(DriveRequestStatusResponse {
request_id: id.clone(),
state: DriveRequestState::Committed,
response: None,
}),
response(DriveRequestStatusResponse {
request_id: "another-request".into(),
state: DriveRequestState::Committed,
response: Some(committed),
}),
WorkspaceResponse {
status: 200,
body: "invalid-json".into(),
},
];
receipts.extend(invalid_completions(&id).into_iter().map(|completion| {
response(DriveRequestStatusResponse {
request_id: id.clone(),
state: DriveRequestState::Committed,
response: Some(completion),
})
}));
for completion in invalid_completions(&id) {
for receipt in &receipts {
client.requests.lock().unwrap().clear();
client
.responses
.lock()
.unwrap()
.extend([Ok(response(&completion)), Ok(receipt.clone())]);
feature.observe(entry("2", "1")).unwrap();
let result = feature
.execute(
"DriveWrite",
&json!({"entry":entry("2", "1").entry,"content":"new"}).to_string(),
&context,
None,
)
.await;
assert!(
matches!(result, Err(DriveError::OutcomeUnknown(ref observed)) if observed == &id)
);
let requests = client.requests.lock().unwrap();
assert_eq!(requests.len(), 2);
assert_eq!(
requests[0].method,
crate::worker::WorkspaceRequestMethod::Post
);
assert_eq!(
requests[1].method,
crate::worker::WorkspaceRequestMethod::Get
);
assert!(requests[1].path.ends_with(&format!("/requests/{id}")));
}
}
}
#[tokio::test]
async fn observation_eviction_never_substitutes_another_entry() {
let feature = feature(Client::new(Vec::new()));