From cfdcbd902b0caa5999d2ff0b1c3b394d1f1b77fc Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 26 Aug 2026 03:18:58 +0200 Subject: [PATCH] Deduplicate strategic wave receipt evidence --- crates/ghostlight-dungeon/src/scheduler.rs | 36 +++++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/crates/ghostlight-dungeon/src/scheduler.rs b/crates/ghostlight-dungeon/src/scheduler.rs index f4de007..b9389ea 100644 --- a/crates/ghostlight-dungeon/src/scheduler.rs +++ b/crates/ghostlight-dungeon/src/scheduler.rs @@ -163,10 +163,7 @@ pub async fn propose_resolution_wave_with_policy( }), ); } - let cell_model_receipt_hashes = stages - .iter() - .map(|stage| stage.receipt.storage_key().to_owned()) - .collect::>(); + let cell_model_receipt_hashes = distinct_model_receipt_hashes(&stages); let mut wave = ResolutionWaveCommit { schema: "ghostlight.resolution_wave_commit.v1".into(), world_revision: campaign.revision, @@ -215,10 +212,7 @@ pub async fn propose_resolution_wave_with_policy( } stages.extend(outcome_stages); wave.activity_outcomes = activity_outcomes; - wave.model_receipt_hashes = stages - .iter() - .map(|stage| stage.receipt.storage_key().to_owned()) - .collect(); + wave.model_receipt_hashes = distinct_model_receipt_hashes(&stages); validate_and_resolve_wave(campaign, &wave)?; let aggregate_receipt_hash = format!( "sha256:{:x}", @@ -232,6 +226,17 @@ pub async fn propose_resolution_wave_with_policy( }) } +fn distinct_model_receipt_hashes(stages: &[ModelStageOutput]) -> Vec { + let mut seen = BTreeSet::new(); + stages + .iter() + .filter_map(|stage| { + let hash = stage.receipt.storage_key().to_owned(); + seen.insert(hash.clone()).then_some(hash) + }) + .collect() +} + async fn project_resolution_demand( model: &dyn ModelPort, campaign: &Campaign, @@ -1588,6 +1593,21 @@ mod tests { assert_eq!(output.wave.cover.cells.len(), 2); assert_eq!(output.wave.appraisals.len(), 2); assert_eq!(output.stages.len(), 7); + assert_eq!( + output.wave.model_receipt_hashes.len(), + output + .wave + .model_receipt_hashes + .iter() + .collect::>() + .len() + ); + let mut repeated_stage = output.stages.clone(); + repeated_stage.push(output.stages[0].clone()); + assert_eq!( + distinct_model_receipt_hashes(&repeated_stage), + output.wave.model_receipt_hashes + ); assert!(model.maximum.load(Ordering::SeqCst) <= 2); validate_and_resolve_wave(&campaign, &output.wave).unwrap(); }