Skip to content

fix(types,executor): one family-capability table decides which statistic a family answers - #938

Merged
zzylol merged 4 commits into
mainfrom
fix/summary-estimate-validation
Oct 26, 2026
Merged

zzylol merged 4 commits into
mainfrom
fix/summary-estimate-validation

Conversation

@zzylol

@zzylol zzylol commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Closes #937.

Why

Which statistic a summary family answers had three sources of truth, and they disagreed (#937):

Readout FamilyCapability::of (IR types) SummaryEstimate validation executor supported_summary_families
Hll / Kmv total no no yes (the kernels returned the distinct count)
DDSketch total yes no yes

A deployment declaring the executor's list told the Planner that an HLL answers a total the IR refuses, and planner rules that read the table (CSE sharing, merged-pane guarantees) believed a DDSketch answers a total the IR rejects. The executor's validate_sketch_evaluation held a fourth, hand-written copy.

What

One table at the IR layer, FamilyCapability::of, decides which statistic a family answers. The IR validation and the executor follow it. The table follows the current IR: no IR-accepted plan changes, except where the table intentionally fixes a row (listed below).

Changed rows

Family-capability table (crates/types/src/ir/schema/family_capability.rs), per-instance states:

Family Before After Why
DDSketch Quantile, Total Quantile the IR never accepted its total
CountSketch ItemFrequency, FrequencyL2 ItemFrequency the IR never accepted L2; its signed counters give no total, and neither the executor nor asap_sketchlib keeps one
CmsWithHeap, CountSketchWithHeap TopK, ItemFrequency TopK the IR accepted top-k only; the executor reads only top-k

Unchanged: Kll, Cms (ItemFrequency, Total), Hll and Kmv (Distinct), UnivMon (Total, Distinct, FrequencyL2, FrequencyEntropy), Theta, and the three Hydra rows.

Executor list (supported_summary_families), now derived from the table:

Family Before After
Hll Total, Distinct Distinct
Kmv Total, Distinct Distinct
DDSketch Total, Quantile Quantile

Other families are unchanged. Cms keeps [Total]: its per-instance item frequency is in the table but listed in UNEVALUATED_READOUTS (the executor reads an item's frequency only from a Hydra grid).

IR validation: what changes

Old category match versus the table, over every family and statistic (pinned by estimate_validation_matches_the_category_match_except_the_fixed_rows). The table's existing rows already said these, and the executor agrees:

  • UnivMon: item frequency and top-k are now rejected (the old Universal category accepted every point count and top-k).
  • HydraCms and HydraCountSketch: the group total is now rejected (the executor already refused it: no group-mass query in asap_sketchlib's Hydra).
  • Count Sketch: the total is now rejected (signed counters give no total).

Every other (family, statistic) pair keeps its old answer, including the DDSketch total (still rejected).

How

  • SummaryEstimate validation calls FamilyCapability::of(family).answers(query) for a sketch state; non-sketch states are still rejected. SketchCategory, SketchKind::category() and the category field are deleted: nothing else read them. Serialized SketchKind JSON loses its category key; old JSON still deserializes, because unknown keys are ignored.
  • Pass 1 no longer offers Count Sketch for a count (realization.rs summary_candidates): Count-Min and UnivMon remain.
  • FamilyCapability::of_per_instance_sketch(algorithm) returns a per-instance family's row. of delegates to it, so the executor can read a row without sketch parameters.
  • Executor: validate_sketch_evaluation accepts a statistic exactly when the table answers it and the executor evaluates that readout. Evaluated readouts are the families in EVALUATED_FAMILIES (HydraCms/HydraKll for Hydra) less UNEVALUATED_READOUTS. It keeps the quantile-range check and the HydraCms group-total message (raised only for a HydraCms family). Top-k stays with the keyed evaluation. supported_summary_families is the table row of each evaluated family, less those readouts.
  • HLL, KMV and DDSketch kernels no longer answer a bare PointCount. Nothing reached that path, because the IR never accepted it.
  • merged_error drops its default for a statistic the family does not answer and returns None instead. Every caller passes an IR-valid estimate, which the table answers. The default existed only for Count Sketch's count, which Pass 1 no longer offers.

Key code interfaces added / modified

crates/types/src/ir/schema/family_capability.rs

impl FamilyCapability {
    /// The capability of a per-subpopulation state of `algorithm`, whatever its
    /// parameters: the table row the IR and a deployment's executor read.
    pub fn of_per_instance_sketch(algorithm: &SketchAlgorithm) -> Self; // new
    /// The capability of `family`; per-instance sketches delegate to `of_sketch`.
    pub fn of(family: &FieldDataType) -> Self; // unchanged signature
    /// The error of `statistic` read from a merged state; `None` when the family
    /// has no sound merge or does not answer `statistic`.
    pub fn merged_error(&self, statistic: &SketchStatistic) -> Option<MergedError>; // was: a default for unanswered statistics
}

crates/types/src/ir/schema/sketch.rs

/// The query category served by a committed sketch.
pub enum SketchCategory { Universal, Quantile, Cardinality, Frequency, TopK } // removed
/// A committed sketch choice: algorithm and parameters. Stays.
pub struct SketchKind {
    /// The committed algorithm.
    algorithm: SketchAlgorithm, // unchanged
    /// Its parameters.
    params: SketchParams, // unchanged
    category: SketchCategory, // removed
}
impl SketchKind {
    pub fn category(&self) -> SketchCategory; // removed
}

crates/executor/src/capability.rs

/// Families the executor builds per instance and reads.
pub(crate) const EVALUATED_FAMILIES: [SketchAlgorithm; 8]; // new
/// Table readouts of an evaluated family the executor does not evaluate yet: (Cms, ItemFrequency).
pub(crate) const UNEVALUATED_READOUTS: [(SketchAlgorithm, StatisticKind); 1]; // new
/// Native when the table answers `query` and the executor evaluates that readout.
/// Errors are bad input (an unanswered or unevaluated statistic, a quantile outside [0, 1]).
pub fn validate_sketch_evaluation(family: &SummaryFamilyType, query: &SketchStatistic) -> Result<(), Error>; // was: its own family/statistic match

crates/executor/src/supported_summaries.rs

/// Each evaluated family's table row, less the unevaluated readouts.
pub fn supported_summary_families() -> Vec<SupportedSummary>; // was: a hand-written list

Usage (end to end, as the tests exercise it):

// What a deployment running this executor declares: Hll -> [Distinct], DDSketch -> [Quantile], ...
let supported = asap_executor::supported_summaries::supported_summary_families();
// Stage 1 reads it through DeploymentCapabilities::supports(family, readout), so Pass 1
// offers no HLL for a count. A plan's SummaryEstimate over a DDSketch state:
//   SummaryEstimate { summary_input: <DDSketch state>, query: PointCount { key: SampleValue, value: None } }
// fails validate_structure() with "evaluation does not match its summary family"
// (FamilyCapability::of(DDSketch).answers(total) == false), and the executor's
// validate_sketch_evaluation rejects the same pair, so the three sources agree.

Before this PR

The IR validated SummaryEstimate by SketchCategory. The table and the executor list were separate hand-maintained copies. They disagreed on DDSketch, Hll and Kmv totals, Count Sketch L2 and total, heap item frequencies, UnivMon item frequency and top-k, and Hydra group totals. The executor's HLL/KMV "total" was the distinct count.

After this PR

There is one table at the IR layer. The IR validation, the executor's evaluation check and its supported list all read it, and a test fails when any of them diverge. Plan selection results are unchanged on the existing suites.

Verification

  • New and updated tests, with the failing-test-first check: under the old category validation (temporarily restored), estimate_validation_matches_the_category_match_except_the_fixed_rows and supported_summary_families_match_the_factory_and_evaluations fail; with this change they pass.
    • asap-types: estimate_validation_matches_the_category_match_except_the_fixed_rows, a_statistic_the_family_does_not_answer_is_rejected (DDSketch total, HLL total, quantile over Count-Min, top-k over KLL, exact accumulator), family_capability_table, merged_error_depends_on_the_readout, summary_capability_facts.
    • asap-executor: supported_summary_families_match_the_factory_and_evaluations now also checks that the IR builds exactly what the table answers, and that the table answers exactly what the executor evaluates for every family it evaluates, except the named unevaluated readouts. It also checks that each accepted evaluation's kernel really reads the readout from a built state.
  • New Pass 1 test a_count_offers_no_count_sketch: no Count Sketch among a count's candidates, and every offered summary answers the total. Table tests pin that Count Sketch does not answer a total.
  • Updated tests that encoded the removed Count Sketch count candidate: a_readout_the_deployment_cannot_answer_prunes_its_family and multi_measure_aggregate_has_one_target_per_measure in tests/per_computation_candidates.rs drop the Count Sketch candidate from the expected lists. combinations_skip_choices_a_whole_expression_candidate_absorbs now expects 5 combinations instead of 6, because the inner count has 3 candidates (accumulator, Count-Min, UnivMon) instead of 4; its structural assertions are unchanged. a_count_from_count_sketch_panes_keeps_its_guarantee is now a_count_from_count_min_panes_keeps_its_guarantee: it checks the same merged-pane property with Count-Min.
  • cargo nextest run -p asap-types -p asap-executor -p asap-logical-asap-optimization -p asap-plan-selection: 1218 passed, 0 failed. No plan-selection expectation changed.
  • cargo fmt --all -- --check, cargo clippy -p asap-types -p asap-executor -p asap-logical-asap-optimization --all-targets -- -D warnings: clean. cargo check --workspace --all-targets: clean.
  • Not run: asap-integration-tests and asap-devtools test binaries. The build host's disk filled while linking them; they compile (cargo check --all-targets).

Design doc

docs/design_docs/proposals/asap-primitive-schema.md drops SketchCategory and SketchKind.category/category(). Its rollup table no longer lists a DDSketch total, Count Sketch L2, or heap item frequencies, and the approximate-count row lists Count-Min and UnivMon only.

Follow-ups (not in this PR)

  • The executor has no per-instance Count-Min item-frequency evaluation and no HydraCountSketch kernel. The table answers both.
  • Pass 1's per-intent candidate families (realization.rs) agree with the table: every offered family answers its intent's statistic. They are an ordered preference list, not a second capability table, so they stay.
  • The DAG viewer examples (tools/dag-viewer/examples/*.json) still carry SketchKind's removed category key; they still load (unknown keys are ignored) and should be regenerated.

🤖 Generated with Claude Code

CI regression fix

Scenario: count by (job) (m) with undeclared deployment summaries. Before this follow-up, the stage-pipeline test expected CountSketch to be generated and then deployment-pruned. After the capability change it is correctly never generated, so the pruning report is empty. The test now checks that empty report while preserving the supported candidate and executor-default assertions. Reproduced the failure locally before the change; the unchanged command passes afterward. No production behavior or accuracy target changed.

Local CI-fix verification: all 47 stage-pipeline tests passed, including the nine-query installation and workload-example acceptance tests. Strict devtools stage-pipeline clippy, formatting, and diff checks passed. Full remote CI is pending.

Additional verification: full cargo test -p asap-integration-tests passed: 324 passed, 0 failed, 23 ignored.

Second CI regression: PromQL count/count_over_time unit-weight tests still required CountSketch. Reproduced failure locally, then changed the expected supported summaries to CMS and UnivMon and explicitly rejected CountSketch; physical DAG update-contract assertions remain. All count-planning tests pass (7 passed, 1 existing ignored), with strict clippy. Full workspace validation is running.

…tic a family answers

The SummaryEstimate validation, FamilyCapability::of and the executor's
supported_summary_families each said which statistic a summary family
answers, and they disagreed (Hll/Kmv total listed only by the executor,
DDSketch total listed by the table and the executor but rejected by the
IR).

- SummaryEstimate validation asks FamilyCapability::answers;
  SketchCategory is deleted.
- Table rows follow the current IR: DDSketch answers quantiles only;
  Count Sketch answers item frequency and total (not L2); the heap
  families answer top-k only.
- The executor follows the table: validate_sketch_evaluation and
  supported_summary_families read FamilyCapability, less an explicit list
  of readouts it does not evaluate yet. HLL, KMV and DDSketch kernels no
  longer answer a bare point count.
- merged_error has no default for an unanswered statistic.

Closes #937

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
@zzylol zzylol added bug Something isn't working ir Pre-ASAP or post-ASAP intermediate representation schema Schema, type, key, and nullability contracts labels Oct 26, 2026
zzylol and others added 3 commits October 26, 2026 14:59
Count Sketch's signed counters give no total, and neither the executor
nor asap_sketchlib keeps one: its table row answers item frequency only,
and Pass 1 no longer offers it for a count. The HydraCms group-total
error names its Hydra kind; of_sketch is renamed of_per_instance_sketch.
The primitive-schema design doc drops SketchCategory and the stale
rollup readouts.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ir Pre-ASAP or post-ASAP intermediate representation schema Schema, type, key, and nullability contracts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Which statistic a summary family answers has three sources of truth (table, IR validation, executor list), and they disagree

1 participant