From b11bb95167755a91616e18342e2e7ce0933ea095 Mon Sep 17 00:00:00 2001 From: zz_y Date: Thu, 10 Sep 2026 18:42:22 -0600 Subject: [PATCH] Reject terminal UnivMon states before standard accumulation --- .../operators/univmon_accumulator.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/data_plane/src/precompute_engine/operators/univmon_accumulator.rs b/data_plane/src/precompute_engine/operators/univmon_accumulator.rs index 769da9e3..b216f8f5 100644 --- a/data_plane/src/precompute_engine/operators/univmon_accumulator.rs +++ b/data_plane/src/precompute_engine/operators/univmon_accumulator.rs @@ -45,6 +45,11 @@ impl UnivMonAccumulator { pub fn from_bytes(bytes: &[u8]) -> Result { let inner = UnivMon::deserialize_from_bytes(bytes) .map_err(|e| format!("invalid UnivMon state: {e}"))?; + if !inner.accepts_standard_updates() { + return Err( + "terminal-mode UnivMon state cannot enter the standard-update accumulator".into(), + ); + } Ok(Self { inner }) } @@ -196,6 +201,17 @@ mod tests { assert!((read(&restored, Statistic::FrequencyEntropy) - 1.0).abs() < 0.01); } + /// Terminal-mode serialization is valid sketchlib state but not this accumulator's update domain. + #[test] + fn terminal_state_is_rejected_before_ingestion_or_merge() { + let mut state = UnivMon::init_univmon(4, 3, 16, 2); + state.fast_insert(&DataInput::U64(1), 1); + let bytes = state.serialize_to_bytes().unwrap(); + assert!(UnivMonAccumulator::from_bytes(&bytes).is_err()); + state.free(); + assert!(UnivMonAccumulator::from_bytes(&state.serialize_to_bytes().unwrap()).is_ok()); + } + /// Pane merge preserves overlapping keys and reset removes the previous window. #[test] fn merge_and_reset_preserve_frequency_semantics() {