Skip to content

feat(plan): L3→L4 binding pass + sketch-vs-exact boundary decision (#98) - #100

Merged
zzylol merged 1 commit into
mainfrom
feat/98-l4-binding-boundary
Jul 6, 2026
Merged

zzylol merged 1 commit into
mainfrom
feat/98-l4-binding-boundary

Conversation

@zzylol

@zzylol zzylol commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Closes #98.

Implements the L3→L4 binding and the sketch-vs-exact boundary in asap-plan (which now depends on asap-sketch — L3 in, L4 out, still no front-end dependency).

boundary.rs — the per-intent accuracy decision

realize(&AggIntent) -> Realization is one exhaustive match over the intent vocabulary, so a new AggIntent variant cannot compile without an explicit decision — no silent fall-through, per the issue's coverage requirement. Three outcomes:

Parameters are sized by inverting each family's error bound against the AccuracyTarget: ε=0.01 → KLL k=200 (matching the design-doc worked example), default_cardinality inverts back to HLL precision 14 exactly, δ→CMS depth ⌈ln 1/δ⌉ (0.01 → 5), with a DEFAULT_DELTA for ε-only targets and saturation for degenerate ε.

bind.rs — the binding pass

bind(&QueryExpr) -> Rc<L4Node> (plus bind_in with an explicit BindingScope for CSE'd roots) walks the L3 tree and fires the boundary decision per node over nested aggregate spines:

  • a sketch realization becomes SummaryAgg (kind+params committed, col resolved from input_col() or SampleValue, by carried) wrapped in the SummaryEstimate readout — the Sketch(kind, params) column type appears on the agg edge and stops at the estimate, per the L4 type invariant;
  • an exact accumulator is a bare SummaryAgg (its state ≡ the value; no readout query exists or is needed);
  • everything else wraps as Logical(...) with the schema lifted to all-Primitive.

Edge schemas reuse the canonical L3 output_schema derivation with only the summary-state column retyped, so per-series label preservation (rate), output_names overrides, and time-index tracking carry over for free.

Conservative fallbacks (documented in the module docs): SummaryExpr::Logical boxes a whole L3 subtree with no L4 children, so a logical operator above a bindable aggregate (Filter/BinaryOp over a quantile) subsumes it unbound; multi-intent Aggregates and HAVING also stay logical. Rewriting through logical parents is the L4 rule engine's job (#6/#33), not this pass's.

Tests

  • Coverage matrix (boundary.rs): all ~44 intent variants pinned to their category, plus assertions that every accumulator pick is agg_is_mergeable and every sketch pick is on a genuinely approximate target.
  • Boundary per accuracy target: same intent, Exact vs ε=0.01 vs ε=0.05 → three decisions; HLL p=14 inversion; (ε,δ) CMS sizing; TopK heap=k; degenerate-ε saturation.
  • Bind pass: KLL+estimate shape and both edge schemas, exact-sum accumulator, per-series rate label preservation, nested per-node binding, logical-parent/HAVING/multi-intent fallbacks, SQL named-column resolution.
  • End-to-end pin (asap-e2e/tests/l4_binding.rs): quantile(0.99, rate(http_requests_total[5m])) from query string → Estimate ∘ SummaryAgg(Kll{200}) ∘ SummaryAgg(Rate) ∘ Logical(TimeRange→Scan), node by node with edge types.

Docs: reconciled the four stale "#98 is tracked/stub" references in design.md and the asap-plan crate docs.

Verified: cargo test --workspace (326 tests, 0 failures), cargo clippy --all-targets clean.

🤖 Generated with Claude Code

Implements the two halves of #98 in asap-plan:

- boundary.rs — the per-intent sketch-vs-exact decision. One exhaustive
  AggIntent → Realization match (a new intent cannot compile without an
  explicit decision): Quantile→KLL (DDSketch candidate), Cardinality→HLL
  (Theta/KMV candidates), TopK→CMS-with-heap, approximate Count→CMS;
  exact mergeable accumulators for Sum/Count/Min-Max/Rate/Increase;
  explicit pass-through for the non-mergeable reducers (Avg/StdDev/
  Variance), classic-bucket HistogramQuantile (#79: not re-sketchable),
  and the ~30 per-series transform intents. Sketch parameters are sized
  by inverting each family's error bound against the AccuracyTarget
  (ε=0.01 → KLL k=200; default_cardinality → HLL p=14; δ → CMS depth).

- bind.rs — the binding pass. Walks the L3 QueryExpr, fires the boundary
  decision per node over nested aggregate spines, and emits the L4
  SummaryExpr/L4Node DAG: sketches become SummaryAgg + SummaryEstimate
  readout (the Sketch(kind, params) column type stops at the estimate),
  exact accumulators a bare SummaryAgg, everything else Logical(...).
  Schemas reuse the canonical L3 derivation with the summary state
  column retyped, so per-series label preservation and output_names
  overrides carry over for free. Conservative fallbacks (logical parents,
  multi-intent nodes, HAVING) are documented; rewriting through logical
  operators is the rule engine's job (#6/#33).

Tests: the AggIntent→SummaryKind coverage matrix (all ~44 variants pinned),
boundary decisions per accuracy target, bind-pass unit tests, and an
end-to-end PromQL-string→L4 pin in asap-e2e
(quantile(0.99, rate(m[5m])) → Estimate ∘ SummaryAgg(KLL) ∘ SummaryAgg(Rate)).

Closes #98

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@zzylol
zzylol force-pushed the feat/98-l4-binding-boundary branch from 385c065 to f565309 Compare July 6, 2026 00:28
@zzylol
zzylol merged commit cf669c4 into main Jul 6, 2026
1 check passed
@zzylol
zzylol deleted the feat/98-l4-binding-boundary branch July 6, 2026 00:28
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.

L4: implement the L3→L4 binding (AggIntent→SummaryKind) and the sketch-vs-exact boundary decision

1 participant