diff --git a/crates/asap-aware-mapping/src/grouping.rs b/crates/asap-aware-mapping/src/grouping.rs index 9ae96f31..7d53e930 100644 --- a/crates/asap-aware-mapping/src/grouping.rs +++ b/crates/asap-aware-mapping/src/grouping.rs @@ -164,6 +164,7 @@ impl<'a> HydraGroupingStrategy<'a> { accuracy: accuracy_model, allocator, evidence, + allow_uncertified_ddsketch_ratios: false, }, } } diff --git a/crates/asap-aware-mapping/src/replacement.rs b/crates/asap-aware-mapping/src/replacement.rs index e705b4fb..d083a24f 100644 --- a/crates/asap-aware-mapping/src/replacement.rs +++ b/crates/asap-aware-mapping/src/replacement.rs @@ -1263,6 +1263,9 @@ pub(crate) struct Models<'a> { pub accuracy: &'a dyn AccuracyModel, pub allocator: &'a dyn AccuracyBudgetAllocator, pub evidence: &'a dyn AccuracyEvidenceProvider, + /// Demo-only escape hatch for exposing a DDSketch quantile-ratio + /// candidate without claiming that its end-to-end error is certified. + pub allow_uncertified_ddsketch_ratios: bool, } impl<'a> Models<'a> { @@ -1275,6 +1278,7 @@ impl<'a> Models<'a> { accuracy: &DEFAULT_ACCURACY_MODEL, allocator: &DEFAULT_ALLOCATOR, evidence: &NO_ACCURACY_EVIDENCE, + allow_uncertified_ddsketch_ratios: false, } } } @@ -1338,6 +1342,7 @@ impl<'a> SketchAlgorithmStrategy<'a> { accuracy: accuracy_model, allocator, evidence: &NO_ACCURACY_EVIDENCE, + allow_uncertified_ddsketch_ratios: false, }, } } @@ -1356,10 +1361,23 @@ impl<'a> SketchAlgorithmStrategy<'a> { accuracy: accuracy_model, allocator, evidence, + allow_uncertified_ddsketch_ratios: false, }, } } + /// Opts a demo or diagnostic caller into DDSketch quantile-ratio + /// candidates when no input-domain evidence is available. + /// + /// Such a candidate carries no [`ResultGuarantee`]. Production planning + /// should use [`Self::with_models_and_evidence`] so the ratio is admitted + /// only when its input domains support a certified error bound. + pub fn with_uncertified_ddsketch_ratios_for_demo(cost_model: &'a dyn CostModel) -> Self { + let mut models = Models::with_default_accuracy(cost_model); + models.allow_uncertified_ddsketch_ratios = true; + Self { models } + } + pub(crate) fn from_models(models: Models<'a>) -> Self { Self { models } } @@ -1390,11 +1408,18 @@ impl<'a> SketchAlgorithmStrategy<'a> { } if intent_override.is_none() && is_supported_exact_binary(root) { if let Ok(Some(node)) = realize_binary(root, self.models, None) { + let rationale = if node.guarantee.is_none() + && self.models.allow_uncertified_ddsketch_ratios + { + "demo-only DDSketch quantile ratio; no certified end-to-end accuracy guarantee" + } else { + "preserve exact PromQL arithmetic over independently realized summary operands" + }; proposals.candidates.push(ReplacementSubDAG { replacement: Replacement::Summary(node), strategy: "SketchAlgorithmStrategy", provenance: ReplacementProvenance::SummaryImplementation, - rationale: "preserve exact PromQL arithmetic over independently realized summary operands".into(), + rationale: rationale.into(), }); } return proposals; @@ -1841,6 +1866,8 @@ fn realize_binary( let direct_ddsketch_ratio = matches!(op, BinaryOpKind::Arithmetic(ArithmeticOpKind::Div)) && shared_quantile_target(lhs, rhs).is_some(); + let allow_uncertified_ddsketch_ratio = + direct_ddsketch_ratio && models.allow_uncertified_ddsketch_ratios; let ratio_target = end_to_end_target .cloned() .or_else(|| shared_quantile_target(lhs, rhs)); @@ -1851,31 +1878,34 @@ fn realize_binary( .as_ref() .and_then(ddsketch_ratio_operand_target) { - let Some(domains) = models + let domains = models .evidence .quantile_input_domain(lhs) .zip(models.evidence.quantile_input_domain(rhs)) - .map(|(lhs, rhs)| [lhs, rhs]) - else { + .map(|(lhs, rhs)| [lhs, rhs]); + if domains.is_none() && !allow_uncertified_ddsketch_ratio { return Ok(None); - }; + } let (alpha, _) = accuracy_budget(&target); - if domains - .iter() - .any(|domain| !domain.supports_ddsketch(alpha)) - { + if domains.as_ref().is_some_and(|domains| { + domains + .iter() + .any(|domain| !domain.supports_ddsketch(alpha)) + }) { return Ok(None); } lhs_node = realize_ddsketch_quantile_operand(lhs, models, &target)?; rhs_node = realize_ddsketch_quantile_operand(rhs, models, &target)?; - for (domain, node) in domains.iter().zip([&lhs_node, &rhs_node]) { - if !ddsketch_quantile_alpha(node) - .is_some_and(|alpha| domain.supports_ddsketch(alpha)) - { - return Ok(None); + if let Some(domains) = domains.as_ref() { + for (domain, node) in domains.iter().zip([&lhs_node, &rhs_node]) { + if !ddsketch_quantile_alpha(node) + .is_some_and(|alpha| domain.supports_ddsketch(alpha)) + { + return Ok(None); + } } } - ratio_domains = Some(domains); + ratio_domains = domains; } } else if let Some(target) = end_to_end_target { let operand_guarantees = [lhs_node.guarantee.as_ref(), rhs_node.guarantee.as_ref()]; @@ -1925,6 +1955,7 @@ fn realize_binary( // Only the domain-proven ratio path may consume approximate operands. // Runtime finite/nonzero checks alone do not establish quantile error bounds. if ratio_domains.is_none() + && !allow_uncertified_ddsketch_ratio && matches!(op, BinaryOpKind::Arithmetic(ArithmeticOpKind::Div)) && [&lhs_node, &rhs_node].iter().any(|node| { !node @@ -1947,6 +1978,7 @@ fn realize_binary( let guarantee = if matches!(op, BinaryOpKind::Arithmetic(ArithmeticOpKind::Div)) && direct_ddsketch_ratio + && ratio_domains.is_some() && ddsketch_quantile_alpha(&lhs_node).is_some() && ddsketch_quantile_alpha(&rhs_node).is_some() { @@ -1974,7 +2006,7 @@ fn realize_binary( .then(|| ResultGuarantee::exact("BinaryOp over exact operands")) }; - if direct_ddsketch_ratio && guarantee.is_none() { + if direct_ddsketch_ratio && guarantee.is_none() && !allow_uncertified_ddsketch_ratio { return Ok(None); } diff --git a/crates/devtools/src/bin/show_post_asap_ir.rs b/crates/devtools/src/bin/show_post_asap_ir.rs index 816ad8c5..9e73fb55 100644 --- a/crates/devtools/src/bin/show_post_asap_ir.rs +++ b/crates/devtools/src/bin/show_post_asap_ir.rs @@ -39,17 +39,19 @@ const ACCURACY: AccuracyTarget = AccuracyTarget::Epsilon(0.01); fn bind_all(expr: &QueryExpr) -> Result>, String> { let root = Rc::new(expr.clone()); let target = TargetSubDAG::new(&root); - let candidates = SketchAlgorithmStrategy::default_cost_model() - .replacements(&target) - .into_iter() - .filter_map(|candidate| match candidate { - ReplacementSubDAG { - replacement: Replacement::Summary(node), - .. - } => Some(node), - _ => None, - }) - .collect::>(); + let candidates = SketchAlgorithmStrategy::with_uncertified_ddsketch_ratios_for_demo( + &asap_aware_mapping::cost_model::DefaultCostModel, + ) + .replacements(&target) + .into_iter() + .filter_map(|candidate| match candidate { + ReplacementSubDAG { + replacement: Replacement::Summary(node), + .. + } => Some(node), + _ => None, + }) + .collect::>(); if candidates.is_empty() { Ok(vec![keep_pre_asap(&root).map_err(|e| e.to_string())?]) @@ -157,4 +159,44 @@ mod tests { assert!(expected > 1, "fixture exposes alternative bindings"); assert_eq!(bind_all(&expr).expect("binding succeeds").len(), expected); } + + #[test] + fn bind_all_exposes_an_uncertified_ddsketch_ratio_for_the_demo() { + let expr = lower_promql_with_data_ingestion_interval( + "quantile_over_time(0.9,data[5m])/quantile_over_time(0.5,data[5m])", + ACCURACY.clone(), + 1_000, + ) + .expect("query lowers to pre-ASAP IR"); + + let candidates = bind_all(&expr).expect("binding succeeds"); + assert_eq!(candidates.len(), 1); + assert!(matches!( + candidates[0].expr, + asap_types::post_asap::SummaryExpr::BinaryOp { .. } + )); + assert!( + candidates[0].guarantee.is_none(), + "the demo escape hatch must not claim a certified ratio bound" + ); + asap_types::post_asap::compile_executable_dag(&candidates[0]) + .expect("the demo candidate remains executable"); + } + + #[test] + fn demo_escape_hatch_does_not_relax_other_approximate_divisions() { + let expr = lower_promql_with_data_ingestion_interval( + "avg_over_time(data[5m])/quantile_over_time(0.5,data[5m])", + ACCURACY.clone(), + 1_000, + ) + .expect("query lowers to pre-ASAP IR"); + + let candidates = bind_all(&expr).expect("binding succeeds"); + assert_eq!(candidates.len(), 1); + assert!(matches!( + candidates[0].expr, + asap_types::post_asap::SummaryExpr::KeepPreAsap(_) + )); + } } diff --git a/docs/design_docs/proposals/asap-aware-mapping/ddsketch-quantile-ratios.md b/docs/design_docs/proposals/asap-aware-mapping/ddsketch-quantile-ratios.md index 8611ebdc..fc9139b8 100644 --- a/docs/design_docs/proposals/asap-aware-mapping/ddsketch-quantile-ratios.md +++ b/docs/design_docs/proposals/asap-aware-mapping/ddsketch-quantile-ratios.md @@ -11,3 +11,16 @@ Certification requires each domain to lie wholly within the pinned mapping's pos The denominator range must exclude zero. True and perturbed quotient ranges must stay finite and outside Float64's subnormal range, with an exact zero numerator allowed. Invalid quantile parameters and missing/invalid proofs do not receive a ratio certificate. Without a certificate the approximate ratio candidate is declined and exact execution remains available. These checks are conservative: an actual window may be safe even when its declared bounds cannot prove it. The final guarantee records both input ranges and their contract identifiers. The integration layer must only provide contracts it enforces for the plan's lifetime. This change adds no runtime guard, fallback executor, or automatic proof inference, and does not change standalone DDSketch readout certification outside this ratio rule. + +## V1 demonstration policy + +`SketchAlgorithmStrategy::with_uncertified_ddsketch_ratios_for_demo` is an +explicit escape hatch for demos and diagnostics that need to inspect the +DDSketch ratio DAG before an evidence provider is integrated. It permits the +candidate when domain evidence is absent, but leaves the root guarantee unset. +It does not turn missing evidence into evidence, and accuracy-enforcing callers +must not treat this candidate as certified. + +The default constructors remain fail-closed. Production callers should use +`with_models_and_evidence`. Runtime or statically enforced domain contracts +remain future work driven by observed v1 correctness needs. diff --git a/docs/user_guide_docs/run-a-query.md b/docs/user_guide_docs/run-a-query.md index 31fe139e..76d1ad90 100644 --- a/docs/user_guide_docs/run-a-query.md +++ b/docs/user_guide_docs/run-a-query.md @@ -101,10 +101,16 @@ your database schema. Use the [library workflow](../develop_docs/library-api.md) to retain workload alternatives and provide your own models. +For the v1 demo, this command also opts into DDSketch quantile-ratio candidates +when no input-domain evidence is available. Those candidates have +`guarantee: None`: they demonstrate the intended post-ASAP shape but do not +claim a certified end-to-end accuracy bound. The normal sketch strategy still +declines such ratios unless the caller supplies sufficient domain evidence. + Each input line is followed by its debug IR or an `ERR:` message. Post-ASAP output may contain summary state, readouts or exact `KeepPreAsap` work. An -approximate target permits approximation; it does not guarantee a legal sketch. -The tool prints plans, not query results. +approximate target permits approximation; it does not guarantee a legal or +certified sketch. The tool prints plans, not query results. ## More inspection commands