Skip to content

feat(control_plane): adopt asap_plan::bind::implement_tree (Step A) - #407

Merged
zzylol merged 1 commit into
mainfrom
feat/implement-tree-adoption-step-a
Jul 22, 2026
Merged

zzylol merged 1 commit into
mainfrom
feat/implement-tree-adoption-step-a

Conversation

@zzylol

@zzylol zzylol commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

First step of the plan-shaped-serving scoping (control_plane/data_plane's query-serving model is currently a flat lookup -- "does some pre-registered accumulator answer this one AggIntent" -- never composing multiple accumulators into a multi-stage plan; Avg = Sum/Count stays archive-only for exactly this reason).

  • asap_plan::bind::implement_tree_in_with already exists upstream and builds a genuinely compositional plan (Rc<L4Node>/SummaryExpr) from an L3 QueryExpr -- but it's deliberately conservative about where it looks for a realizable Aggregate: any non-Aggregate node (Filter/Window/Project/...) immediately wraps the whole subtree as Logical, with no attempt to recurse past it. Per asap-plan's own module doc, that's a deliberate deployment-specific decision it doesn't model.
  • New control_plane/src/asap_tier_implement.rs: collect_aggregate_roots mirrors asap_tier_analysis::collect_agg_intents's exact recursion through every QueryExpr variant, but collects Aggregate subtree roots instead of flattening AggIntents, so implement_tree_in_with can realize each independently. implement_promql_for_asap_tier ties parsing + this walk + implementation together.
  • Known, tracked gap, not yet wired into the live serving path: asap-plan has no opinion on AggIntent::Extension (control_plane's deployment-specific Frequency intent), so a Frequency-shaped query under-realizes to Logical here today, unlike capability_for's as_frequency special case on the flat path. Pinned by implement_frequency_as_agg_test rather than left as a silent trap. Closing it is the remaining scope before this can replace the flat analyzer -- either an asap-plan extension hook, or a hand-rolled L4Node construction here (today impossible without reimplementing asap_plan::bind's private helpers).

Test plan

  • cargo build --workspace -- clean
  • cargo test -p control_plane --lib asap_tier_implement -- 8/8 passed, each grounded against the flat path's own existing tests (bare selector, sum, quantile, avg_over_time, nested aggregate-under-aggregate, filter-wrapped aggregate, the frequency gap)
  • cargo test -p asap_types -p control_plane -p data_plane --lib -- control_plane 777 passed / 1 pre-existing unrelated failure (confirmed identical on main), data_plane/asap_types unaffected
  • rustfmt applied to touched files only

🤖 Generated with Claude Code

Plan-shaped-serving scoping, Step A: asap_tier_analysis's
collect_agg_intents flattens every AggIntent anywhere in a query tree
into one list, then checks each independently against capability_for
-- it never composes multiple accumulators into a multi-stage plan
(Avg = Sum/Count stays archive-only for exactly this reason).
asap_plan::bind::implement_tree_in_with already builds a genuinely
compositional plan (Rc<L4Node>/SummaryExpr), but is deliberately
conservative about where it looks for a realizable Aggregate: hitting
any non-Aggregate node (Filter, Window, Project, ...) wraps the whole
subtree as Logical with no attempt to recurse past it -- "rewriting
through logical parents is the L4 rule engine's job", a
deployment-specific decision asap-plan explicitly doesn't model.

Adds asap_tier_implement.rs: collect_aggregate_roots mirrors
collect_agg_intents's exact recursion through every QueryExpr variant,
but collects Aggregate subtree roots instead of flattening AggIntents,
so implement_tree_in_with can be handed each one independently.
implement_promql_for_asap_tier ties parsing + this walk +
implementation together, returning one Rc<L4Node> per root.

Known, tracked gap (not wired into the live serving path for this
reason): asap-plan has no opinion on AggIntent::Extension
(control_plane's deployment-specific Frequency intent rides in one) --
it always returns Implementation::PassThrough, so a Frequency-shaped
query under-realizes to Logical here today, unlike capability_for's
as_frequency special case on the flat path. Pinned by
implement_frequency_as_agg_test rather than silently left as a trap.
Closing it is Step A's remaining scope: either an asap-plan extension
hook, or a hand-rolled SummaryAgg/SummaryEstimate construction here
(today impossible without reimplementing asap_plan::bind's private
L4Node-building helpers).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@zzylol
zzylol merged commit 8273020 into main Jul 22, 2026
@zzylol
zzylol deleted the feat/implement-tree-adoption-step-a branch July 22, 2026 16:47
zzylol added a commit that referenced this pull request Jul 24, 2026
This doc and data_plane/docs/l4node-plan-executor-design.md (Step C,
#409, itself following Step A/#407 and Step B/#408) were written four
days apart and never cross-referenced each other. §6's RoutingIndex
assumed a flat one-query-to-one-materialization model that predates
L4Node's tree shape and doesn't account for SummaryExecutor::find_candidates
(ASAPController#155, the serving-time counterpart to this section).

As originally written, §6 would reintroduce the exact bug AccumulatorSpec
(#401) was built to close: step 5's Capability::is_satisfied_by is
family-level only and can't guarantee two candidates actually share
(SketchKind, SketchParams), which SummaryMerge requires.

Corrects three things:
- Granularity: RoutingIndex's Tier-2 lookup must be invocable per L4Node
  leaf (find_candidates is called once per SummaryAgg, possibly several
  times for one nested query), not only once per whole query.
- Match precision: find_candidates needs exact (SummaryKind,
  SummaryParams) matching via AccumulatorSpec, not family-level
  Capability -- required for anything that can feed a SummaryMerge.
- Selection semantics: find_candidates must return every exact match for
  merge_states to fold, not rank-and-pick-one like the original
  whole-query mode.

Both consumption modes can share the same columnar/interned Tier-2
structure (§6.1) -- only match precision and return shape differ by
caller. No code changes; this is a design-doc correction so
implementation (of either this or #409) doesn't have to be redone once
the two are compared.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
zzylol added a commit that referenced this pull request Jul 24, 2026
Phase 1 wasn't done as scoped (#391 closed unmerged; #392 "Phase 1b"
substituted a bigger structural fix -- direct git-dep on ASAPController's
IR crates instead of an in-tree vocabulary copy-merge). Phase 3 is
substantially done already (capability_for() routes Sum/Min/Max/Rate/
Increase to exact-agg on main) but not via this plan's sequencing, and
its own documented blocker (missing analyzer_parity_tests corpus) is
still unresolved. Phases 4-5 haven't started.

Also flags an unplanned parallel thread (#407/#408 Step A/B, merged;
#409 Step C, open) that adopts asap_plan::bind::implement_tree /
asap_sketch::L4Node directly and overlaps with what Phases 4-5 were
meant to deliver -- cross-referenced against the RoutingIndex
reconciliation just landed on design-backend-plan-wire-format.md (#389)
so Phases 4-5 get re-scoped against what that thread actually ships
before anyone executes them as originally written.

No process/plan changes here beyond recording status -- this is the
same kind of staleness correction this doc already applied to
ASAPController/docs/migration-plan.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant