From 327fdd5fc2520b236b2b9e505b1458749594963b Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Wed, 5 Aug 2026 09:08:11 +0800 Subject: [PATCH] fix(replication): report FAILED when replication put options cannot be built (#5717) Both per-target replication methods assign the optimistic Completed status to rinfo before building put options, and the Err branch of replication_put_object_options returned rinfo unchanged. Since #5633 made the source encryption classification case-insensitive, managed SSE sources are rejected at this gate, and the rejection was reported as successful replication: the source object was marked COMPLETED, ObjectReplicationComplete was emitted, and nothing existed on the target. Set replication_status = Failed (and record the error in the replicate_object branch) so the composite status, the OperationFailedReplication event, and MRF retries reflect the fail-closed outcome. This restores the contract pinned by test_bucket_replication_sse_kms_failure_contract, which timed out in the e2e-replication-nightly runs on 2026-08-03 and 2026-08-04. --- .../src/bucket/replication/replication_resyncer.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/ecstore/src/bucket/replication/replication_resyncer.rs b/crates/ecstore/src/bucket/replication/replication_resyncer.rs index a351b0aa7..67402340d 100644 --- a/crates/ecstore/src/bucket/replication/replication_resyncer.rs +++ b/crates/ecstore/src/bucket/replication/replication_resyncer.rs @@ -2550,6 +2550,12 @@ impl ReplicateObjectInfoExt for ReplicateObjectInfo { let (put_opts, is_multipart) = match replication_put_object_options(&tgt_client.storage_class, &object_info) { Ok((put_opts, is_mp)) => (put_opts, is_mp), Err(e) => { + // Unsupported source metadata (e.g. managed SSE) is a fail-closed + // condition: report FAILED so the composite status and the + // OperationFailedReplication event reflect that nothing reached + // the target, instead of leaking the optimistic Completed above. + rinfo.replication_status = ReplicationStatusType::Failed; + rinfo.error = Some(e.to_string()); warn!( event = EVENT_RESYNC_TARGET_OPERATION_FAILED, component = LOG_COMPONENT_ECSTORE, @@ -2954,6 +2960,11 @@ impl ReplicateObjectInfoExt for ReplicateObjectInfo { let (put_opts, is_multipart) = match replication_put_object_options(&tgt_client.storage_class, &object_info) { Ok((put_opts, is_mp)) => (put_opts, is_mp), Err(e) => { + // Unsupported source metadata (e.g. managed SSE) is a fail-closed + // condition: report FAILED so the composite status and the + // OperationFailedReplication event reflect that nothing reached + // the target, instead of leaking the optimistic Completed above. + rinfo.replication_status = ReplicationStatusType::Failed; rinfo.error = Some(e.to_string()); warn!( event = EVENT_RESYNC_TARGET_OPERATION_FAILED,