From 32ab6efb1db65d474df20a979218058c551e2ae9 Mon Sep 17 00:00:00 2001 From: zz_y Date: Thu, 10 Sep 2026 07:41:36 -0600 Subject: [PATCH 1/2] fix: enforce post-ASAP summary consistency --- crates/types/src/post_asap/executable_dag.rs | 35 ++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/crates/types/src/post_asap/executable_dag.rs b/crates/types/src/post_asap/executable_dag.rs index 490f5e3d..79a10579 100644 --- a/crates/types/src/post_asap/executable_dag.rs +++ b/crates/types/src/post_asap/executable_dag.rs @@ -72,7 +72,9 @@ pub enum ExecutableOperatorPayload { operator: BinaryOperator, }, CandidateTopK { - k: usize, + /// Fixed-width transport value; runtimes validate conversion to their + /// local collection index type at installation. + k: u64, grouping: GroupKeys, completeness: CandidateCompleteness, }, @@ -191,6 +193,12 @@ pub enum ExecutableDagValidationError { Cycle, #[error("post-ASAP node {0:?} is not reachable from the root")] UnreachableNode(PostAsapNodeId), + #[error("summary aggregate node {node:?} output schema does not contain its declared family")] + SummaryFamilySchemaMismatch { node: PostAsapNodeId }, + #[error( + "summary aggregate node {node:?} declares grouping inconsistent with its sketch state" + )] + SummaryGroupingMismatch { node: PostAsapNodeId }, } impl PostAsapDagDocument { @@ -227,6 +235,29 @@ impl ExecutableDag { actual, }); } + if let ExecutableOperatorPayload::SummaryAgg { + family, grouping, .. + } = &node.payload + { + let mut found_family = false; + for field in &node.output_schema.fields { + if &field.dtype == family { + found_family = true; + } + if let SummaryFamilyType::Sketch(_, schema_grouping) = &field.dtype { + if schema_grouping != grouping { + return Err(ExecutableDagValidationError::SummaryGroupingMismatch { + node: node.id, + }); + } + } + } + if !found_family { + return Err(ExecutableDagValidationError::SummaryFamilySchemaMismatch { + node: node.id, + }); + } + } } if !nodes.contains_key(&self.root) { return Err(ExecutableDagValidationError::MissingRoot(self.root)); @@ -413,7 +444,7 @@ pub fn compile_executable_dag_with_node_ids( completeness, .. } => ExecutableOperatorPayload::CandidateTopK { - k: *k, + k: u64::try_from(*k).expect("usize always fits into the u64 wire count"), grouping: grouping.clone(), completeness: completeness.clone(), }, From b6db64a9b5471dfabecbf081dbe8fadd80073c67 Mon Sep 17 00:00:00 2001 From: zz_y Date: Thu, 10 Sep 2026 07:59:25 -0600 Subject: [PATCH 2/2] fix(tests): compare fixed-width candidate counts --- crates/integration-tests/tests/promql_to_post_asap.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/integration-tests/tests/promql_to_post_asap.rs b/crates/integration-tests/tests/promql_to_post_asap.rs index 7207cffe..b6e18a62 100644 --- a/crates/integration-tests/tests/promql_to_post_asap.rs +++ b/crates/integration-tests/tests/promql_to_post_asap.rs @@ -312,7 +312,7 @@ fn counter_weighted_topk_uses_candidates_only_for_membership_and_exact_values_fo k, grouping, completeness: CandidateCompleteness::Certified { .. }, - } if *k == expected_k && grouping.is_empty() && !grouping.is_without() + } if *k == expected_k as u64 && grouping.is_empty() && !grouping.is_without() ))); assert!(executable.nodes.iter().any(|node| matches!( &node.payload,