From 2086ade96705e0319c1893fce5d8e0e1f72c3c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E7=99=BB=E5=B1=B1?= Date: Sun, 23 Aug 2026 08:41:31 +0800 Subject: [PATCH] fix(scanner): preserve restore expiry semantics --- .../src/scanner_folder/item_actions.rs | 55 +++++++++++++++---- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/crates/scanner/src/scanner_folder/item_actions.rs b/crates/scanner/src/scanner_folder/item_actions.rs index d49a08e3b..dfce9363e 100644 --- a/crates/scanner/src/scanner_folder/item_actions.rs +++ b/crates/scanner/src/scanner_folder/item_actions.rs @@ -249,6 +249,10 @@ fn resolve_sizes(object_infos: &[ObjectInfo]) -> Vec { } fn lifecycle_rule_has_size_filter(lifecycle: &BucketLifecycleConfiguration, rule_id: &str) -> bool { + if rule_id.is_empty() { + return false; + } + let filter_has_size = |filter: &s3s::dto::LifecycleRuleFilter| { filter.object_size_greater_than.is_some() || filter.object_size_less_than.is_some() @@ -260,21 +264,19 @@ fn lifecycle_rule_has_size_filter(lifecycle: &BucketLifecycleConfiguration, rule lifecycle .rules .iter() - .find(|rule| rule.id.as_deref().unwrap_or_default() == rule_id) + .find(|rule| rule.id.as_deref() == Some(rule_id)) .and_then(|rule| rule.filter.as_ref()) .is_some_and(filter_has_size) } fn lifecycle_event_allowed(resolution: &SizeResolution, event: &Event, lifecycle: &BucketLifecycleConfiguration) -> bool { match resolution { - // Corrupt metadata cannot safely authorize a destructive action, even - // when the evaluator happened to produce a time-only event. - SizeResolution::Corrupt { .. } => false, - // A valid-but-unknown logical size may still execute lifecycle - // actions whose rule is independent of object-size predicates. The - // evaluator has already selected the rule; only that rule's filter - // can make the missing logical value action-critical. - SizeResolution::Unknown { .. } => !lifecycle_rule_has_size_filter(lifecycle, &event.rule_id), + // Missing or invalid logical size only defers actions whose selected + // rule actually depends on that size. Time/version-only actions retain + // their existing semantics, including intrinsic events without a rule ID. + SizeResolution::Unknown { .. } | SizeResolution::Corrupt { .. } => { + !lifecycle_rule_has_size_filter(lifecycle, &event.rule_id) + } SizeResolution::Known { .. } => true, } } @@ -1571,7 +1573,7 @@ mod tests { }, &BucketLifecycleConfiguration::default() )); - assert!(!lifecycle_event_allowed( + assert!(lifecycle_event_allowed( &SizeResolution::Corrupt { physical: 12, reason: SizeResolutionReason::InvalidDeclaredSize, @@ -1582,6 +1584,39 @@ mod tests { }, &BucketLifecycleConfiguration::default() )); + assert!(!lifecycle_event_allowed( + &SizeResolution::Corrupt { + physical: 12, + reason: SizeResolutionReason::InvalidDeclaredSize, + }, + &Event { + action: IlmAction::DeleteAction, + rule_id: "size".to_string(), + ..Default::default() + }, + &size_filtered + )); + assert!(!lifecycle_rule_has_size_filter( + &BucketLifecycleConfiguration { + rules: vec![s3s::dto::LifecycleRule { + status: s3s::dto::ExpirationStatus::from_static(s3s::dto::ExpirationStatus::ENABLED), + expiration: None, + abort_incomplete_multipart_upload: None, + del_marker_expiration: None, + id: None, + filter: Some(s3s::dto::LifecycleRuleFilter { + object_size_greater_than: Some(1), + ..Default::default() + }), + noncurrent_version_expiration: None, + noncurrent_version_transitions: None, + prefix: None, + transitions: None, + }], + ..Default::default() + }, + "" + )); assert!(lifecycle_event_allowed( &SizeResolution::Known { logical: 10,