From 158c613d0e1e71d10535add438d74c331fc1d2df Mon Sep 17 00:00:00 2001 From: houseme Date: Tue, 8 Sep 2026 21:48:42 +0800 Subject: [PATCH] test(heal): cover MRF rollback mirror filtering (#7501) Add a regression oracle that keeps scoped-only MRF responsibilities in the authoritative runtime snapshot while omitting them from the v1 legacy rollback mirror. Co-authored-by: zhi22915 --- crates/heal/src/heal/mrf_queue.rs | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/crates/heal/src/heal/mrf_queue.rs b/crates/heal/src/heal/mrf_queue.rs index 2cf43814d..1d40fb088 100644 --- a/crates/heal/src/heal/mrf_queue.rs +++ b/crates/heal/src/heal/mrf_queue.rs @@ -1473,6 +1473,62 @@ mod tests { assert!(!snapshot.is_empty(), "the resident intent remains journalable after rejection"); } + #[test] + fn rollback_legacy_payload_omits_scoped_only_responsibilities() { + let mut scoped = intent("rollback-bucket", "scoped-only-object", 0); + scoped.kind = MrfKind::PartialWrite; + scoped.scope = Some(rustfs_common::mrf_channel::MrfScope { + pool_index: 3, + set_index: 7, + }); + let compat = intent("rollback-bucket", "v1-compatible-object", 0); + + let mut runtime = MrfRuntime { + queue: MrfQueue::new(4, usize::MAX), + config: MrfConsumerConfig::default(), + checkpoint_owner: Uuid::new_v4(), + next_checkpoint_sequence: 1, + new_since_flush: 0, + dirty: true, + journal_on_disk: false, + retain_replay_journal: false, + durable_replay_anchors: Vec::new(), + replay_cleanup: None, + runtime_checkpoint: None, + backoff_until: None, + }; + assert_eq!(runtime.queue.try_push_typed(scoped.clone()), MrfQueuePushResult::Enqueued); + assert_eq!(runtime.queue.try_push_typed(compat.clone()), MrfQueuePushResult::Enqueued); + + let (authoritative, legacy) = runtime.snapshot(); + let (authoritative_decoded, authoritative_truncated) = decode_journal(&authoritative); + assert_eq!(authoritative_truncated, 0); + assert_eq!(authoritative_decoded.len(), 2); + assert!( + authoritative_decoded + .iter() + .any(|intent| intent.object.as_ref() == "scoped-only-object" && intent.scope == scoped.scope), + "new readers must retain the scoped partial-write responsibility" + ); + + let (legacy_decoded, legacy_truncated) = decode_journal(&legacy); + assert_eq!(legacy_truncated, 0); + assert_eq!(legacy_decoded.len(), 1, "rollback payload must contain one v1-compatible record"); + let legacy_record = legacy_decoded.first().expect("one rollback-compatible record"); + assert_eq!(legacy_record.bucket, compat.bucket); + assert_eq!(legacy_record.object, compat.object); + assert_eq!(legacy_record.version_id, compat.version_id); + assert_eq!(legacy_record.kind, compat.kind); + assert_eq!(legacy_record.scope, None); + assert_eq!(legacy_record.attempts, compat.attempts); + assert!( + !legacy + .windows(b"scoped-only-object".len()) + .any(|window| window == b"scoped-only-object"), + "legacy rollback bytes must not disguise a scoped-only responsibility as an unscoped record" + ); + } + #[test] fn mrf_dedupe_failure_releases_key_for_retry() { let mut queue = MrfQueue::new(1, usize::MAX);