@@ -1534,24 +1534,23 @@ fn accuracy_check(
15341534 {
15351535 format ! (
15361536 "q{query}: the output's guarantee holds for each value, and the number \
1537- of values one evaluation returns is not bound (no declared series count \
1538- for a source a PromQL vector reads; no bound on a SQL result's groups \
1539- yet), \
1540- so it does not meet whole-result target {target:?}, and the deployment \
1541- has no evidence that it meets it"
1537+ of values one evaluation returns is not bound (no declared series \
1538+ count for a source a PromQL vector reads; no bound on a SQL result's \
1539+ groups yet), so it does not meet whole-result target {target:?}, and \
1540+ the deployment has no evidence that it meets it"
15421541 )
15431542 }
15441543 Some ( g) => format ! (
1545- "q{query}: the output guarantees a {:?} bound {:?}, failure probability {:?}, \
1546- which misses target {target:?}, and the deployment has no evidence that it \
1547- meets it",
1544+ "q{query}: the output guarantees a {:?} bound {:?}, failure probability \
1545+ {:?}, which misses target {target:?}, and the deployment has no evidence \
1546+ that it meets it",
15481547 g. spec. accuracy_metric,
15491548 g. spec. error. evaluate( ) ,
15501549 g. spec. failure_probability. evaluate( ) ,
15511550 ) ,
15521551 None => format ! (
1553- "q{query}: the output has no guarantee, and the deployment has no evidence \
1554- that it meets target {target:?}"
1552+ "q{query}: the output has no guarantee, and the deployment has no \
1553+ evidence that it meets target {target:?}"
15551554 ) ,
15561555 } ,
15571556 ) ) ;
@@ -4249,6 +4248,87 @@ mod tests {
42494248 }
42504249 }
42514250
4251+ /// Deployment evidence of a large absolute error (1.0) for every output.
4252+ struct EveryOutputMeasuredLoose ;
4253+
4254+ impl AccuracyEvidenceProvider for EveryOutputMeasuredLoose {
4255+ fn output_guarantee ( & self , _output : & QueryRoot ) -> Option < AccuracyGuarantee > {
4256+ Some ( AccuracyGuarantee :: new (
4257+ AccuracyMetric :: ABSOLUTE ,
4258+ BoundExpr :: Constant { value : 1.0 } ,
4259+ ProbabilityExpr :: Constant { value : 1e-6 } ,
4260+ vec ! [ ] ,
4261+ ) )
4262+ }
4263+ }
4264+
4265+ /// A model that accepts every output that has no guarantee.
4266+ struct AcceptsEveryOutputWithoutGuarantee ;
4267+
4268+ impl AccuracyModel for AcceptsEveryOutputWithoutGuarantee {
4269+ fn satisfies ( & self , guarantee : & AccuracyGuarantee , target : & AccuracyTarget ) -> bool {
4270+ BuiltinAccuracyModel . satisfies ( guarantee, target)
4271+ }
4272+
4273+ fn accepts_output_without_guarantee (
4274+ & self ,
4275+ _output : & QueryRoot ,
4276+ _target : & AccuracyTarget ,
4277+ ) -> bool {
4278+ true
4279+ }
4280+ }
4281+
4282+ /// The model's hook decides only when there is no guarantee at all: an
4283+ /// output with no theoretical guarantee whose evidence guarantee misses
4284+ /// the target is rejected as `AccuracyTargetMissed`, though the hook
4285+ /// would accept it. Without that evidence the hook accepts it.
4286+ #[ test]
4287+ fn a_missed_evidence_guarantee_is_not_overridden_by_the_hook ( ) {
4288+ let target = AccuracyTarget :: max_error ( 0.01 ) . failure_probability ( 0.001 ) ;
4289+ let candidates = all_candidates (
4290+ "quantile_over_time(0.99, m[1m]) - quantile_over_time(0.5, m[1m])" ,
4291+ & target,
4292+ ) ;
4293+ let summaries: Vec < _ > = candidates
4294+ . iter ( )
4295+ . filter ( |c| has_summary_estimate ( c) )
4296+ . map ( |c| c. id . as_str ( ) )
4297+ . collect ( ) ;
4298+ assert ! ( !summaries. is_empty( ) ) ;
4299+ let select = |evidence : & dyn AccuracyEvidenceProvider | {
4300+ select_plan (
4301+ & candidates,
4302+ & [ entry_with (
4303+ target. clone ( ) . accuracy_metric ( AccuracyMetric :: ABSOLUTE ) ,
4304+ repeating ( 10_000 ) ,
4305+ ) ] ,
4306+ & data ( ) ,
4307+ PlanningModels :: builtin ( )
4308+ . with_accuracy ( & AcceptsEveryOutputWithoutGuarantee )
4309+ . with_evidence ( evidence) ,
4310+ )
4311+ . unwrap ( )
4312+ } ;
4313+ let loose = select ( & EveryOutputMeasuredLoose ) ;
4314+ for id in & summaries {
4315+ let rejection = loose. rejected . iter ( ) . find ( |r| r. id == * id) . unwrap ( ) ;
4316+ assert_eq ! (
4317+ rejection. cause,
4318+ RejectionCause :: AccuracyTargetMissed ,
4319+ "{}" ,
4320+ rejection. reason
4321+ ) ;
4322+ }
4323+ let no_evidence = select ( & NoAccuracyEvidence ) ;
4324+ for id in & summaries {
4325+ assert ! (
4326+ no_evidence. accepted[ * id] [ 0 ] . accepted_without_guarantee,
4327+ "{id}"
4328+ ) ;
4329+ }
4330+ }
4331+
42524332 /// `candidates` with every state node (a summary build or an exact
42534333 /// aggregate) kept at ingestion time, so selection charges its kept
42544334 /// bytes every second (a `MatAtQuery` state is held only for one
0 commit comments