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:
@@ -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()));
|
||||
|
||||
Reference in New Issue
Block a user