Skip to content

refactor(planner): bundle deployment capabilities with models - #553

Draft
zzylol wants to merge 1 commit into
stack/528-10-univmonfrom
stack/528-11-deployment-inputs
Draft

zzylol wants to merge 1 commit into
stack/528-10-univmonfrom
stack/528-11-deployment-inputs

Conversation

@zzylol

@zzylol zzylol commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Problem: runtime capabilities are passed with the planning clock, not with the other deployment inputs

#509 §Goal and §Stages say the planner takes three groups of inputs, and that the deployment supplies one of them:

| Deployment inputs | Empirical cost model, empirical accuracy model, and execution capabilities |

The deployment only supplies inputs and executes the plan: it provides its empirical cost model, empirical accuracy model and capabilities, but never plans queries or selects plans.

#509 leaves the structure as a TODO ("TODO: define this data structure, #525"). #509 §3 Plan selection then rejects "every candidate that … needs a capability the deployment lacks".

Before this PR, the cost and accuracy models were in PlanningModels, but the runtime lifecycle capabilities were in LifecycleInput, next to the planning clock and horizon:

// Before (crates/asap-aware-mapping/src/pass/mod.rs)
pub struct PlanningModels<'a> {
    pub cost: &'a dyn CostModel,
    pub accuracy: &'a dyn AccuracyModel,
    pub evidence: &'a dyn AccuracyEvidenceProvider,
}
pub struct LifecycleInput {
    pub now_ms: u64,
    pub horizon: Option<Horizon>,
    pub capabilities: SummaryMaintenanceLifecycleCapabilities, // deployment fact, mixed with the clock
}

// Every caller had to pass capabilities together with the clock:
LifecycleInput::new(NOW_MS, SummaryMaintenanceLifecycleCapabilities::default())

now_ms and horizon describe this planning call. The capabilities describe what the deployment's runtime can do. They are a deployment input like the cost and accuracy models, so they belong with them.

Also missing: no test drove restricted capabilities through the public planner (e2e_plan) and checked that the lifecycle alternatives the runtime cannot run are not selectable.

Scope. This PR covers the "execution capabilities" part of #509's deployment inputs, for runtime lifecycle capabilities only (step 1 of #525's proposal). It does not touch SummaryMaintenanceCapabilities (what a summary algorithm can do), the value-operation capability hooks on CostModel, or #511 operator capabilities.

Proposed method

  1. Move the field capabilities: SummaryMaintenanceLifecycleCapabilities from LifecycleInput to PlanningModels. PlanningModels is now the one bundle of deployment inputs: cost model, accuracy model, accuracy evidence and runtime capabilities. No new struct.
  2. PlanningModels::new and PlanningModels::builtin set capabilities to SummaryMaintenanceLifecycleCapabilities::ALL. Default for the capabilities type is already ALL, so callers that used ::default() keep the same behavior.
  3. Add the builder PlanningModels::with_capabilities, matching with_cost, with_accuracy and with_evidence.
  4. LifecycleInput::new now takes only now_ms. LifecycleInput keeps the planning clock and the optional horizon.
  5. MajorPass reads models.capabilities instead of lifecycle.capabilities at its three call sites: global selection (global_selection_with_summary_maintenance_lifecycles) and the two final-assembly calls (plan_assembled_dag).

Where it lives: the input to the optimization pass and the public planner facade. The capability check itself is unchanged. It still runs in the lifecycle stage (physical materialization choice and selection), which marks an alternative the runtime cannot run as UnsupportedByRuntime.

Key code interfaces

crates/asap-aware-mapping/src/pass/mod.rs

#[derive(Clone, Copy)]
#[non_exhaustive]
pub struct PlanningModels<'a> {
    pub cost: &'a dyn CostModel,
    pub accuracy: &'a dyn AccuracyModel,
    pub evidence: &'a dyn AccuracyEvidenceProvider,
    pub capabilities: SummaryMaintenanceLifecycleCapabilities, // new
}

impl<'a> PlanningModels<'a> {
    pub fn new(cost: &'a dyn CostModel, accuracy: &'a dyn AccuracyModel,
               evidence: &'a dyn AccuracyEvidenceProvider) -> Self; // capabilities = ALL
    pub fn builtin() -> PlanningModels<'static>;                    // capabilities = ALL
    pub fn with_capabilities(mut self,
        capabilities: SummaryMaintenanceLifecycleCapabilities) -> Self; // new
}

#[derive(Clone, Copy)]
#[non_exhaustive]
pub struct LifecycleInput {
    pub now_ms: u64,
    pub horizon: Option<Horizon>,
    // `capabilities` removed
}

impl LifecycleInput {
    pub fn new(now_ms: u64) -> Self; // was new(now_ms, capabilities)
}

The capabilities type is unchanged (crates/asap-aware-mapping/src/summary_maintenance_lifecycle.rs):

pub struct SummaryMaintenanceLifecycleCapabilities {
    pub supports_ephemeral: bool,
    pub supports_prepared: bool,
    pub supports_shared: bool,
    pub supports_continuously_maintained: bool,
}
impl SummaryMaintenanceLifecycleCapabilities { pub const ALL: Self; }

Usage:

let models = PlanningModels::builtin()
    .with_cost(&costs)
    .with_capabilities(SummaryMaintenanceLifecycleCapabilities {
        supports_ephemeral: true,
        supports_prepared: false,
        supports_shared: false,
        supports_continuously_maintained: false,
    });
e2e_plan(UserInput::new(&workload, frontend, models, LifecycleInput::new(NOW_MS))).await?;

Fields

PlanningModels

Field / method Type Meaning Set by
cost &dyn CostModel Deployment cost model (unchanged) Caller; builtin() uses DefaultCostModel
accuracy &dyn AccuracyModel Deployment accuracy model (unchanged) Caller; builtin() uses DefaultAccuracyModel
evidence &dyn AccuracyEvidenceProvider Accuracy evidence for the deployment (unchanged) Caller; builtin() uses NoAccuracyEvidence
capabilities SummaryMaintenanceLifecycleCapabilities Which summary-maintenance lifecycles the deployment's runtime can run. New in this struct. Caller via with_capabilities; defaults to ALL in new and builtin
new(cost, accuracy, evidence) constructor Builds the bundle from three models; capabilities default to ALL Caller
builtin() constructor Built-in models; capabilities ALL Caller
with_capabilities(capabilities) builder Replaces capabilities, returns Self Caller

LifecycleInput

Field / method Type Meaning
now_ms u64 Planning clock, Unix milliseconds. Must agree with the frontend clock (checked by rejects_disagreeing_planning_clocks).
horizon Option<Horizon> Optimization horizon in seconds. Needed to turn recurring demand into a finite total cost. None keeps horizon-dependent alternatives unselectable. Set with with_horizon.
new(now_ms) constructor Clock only; horizon = None

SummaryMaintenanceLifecycleCapabilities (unchanged; listed because it is now a PlanningModels field)

Field Meaning when true Lifecycle it gates
supports_ephemeral The runtime can build a fresh state for each invocation and retire it afterwards SummaryMaintenanceLifecycle::Ephemeral
supports_prepared The runtime can build state before a predictable execution and keep it until that execution window ends Prepared { activate_at, retire_at }
supports_shared The runtime can keep one state and reuse it across several reads Shared { retention }
supports_continuously_maintained The runtime can keep state current by applying arriving data ContinuouslyMaintained
ALL const with all four flags true; also Default —

A false flag makes the matching alternative carry SummaryMaintenanceLifecycleRejection::UnsupportedByRuntime. A true flag does not make an alternative selectable by itself: it can still be rejected for missing workload evidence or unknown cost.

Examples

Restricted runtime, through the public planner. Test deployment_inputs_control_lifecycle_capabilities in crates/planner/tests/e2e_plan.rs:

  • Input: SQL batch SELECT COUNT(DISTINCT l_orderkey) FROM lineitem, PlanningModels::builtin().with_capabilities(…) with only supports_ephemeral = true, and LifecycleInput::new(NOW_MS).
  • What happens: e2e_plan runs the frontend and MajorPass. MajorPass passes models.capabilities to lifecycle selection and final assembly.
  • Output: planning succeeds. In every plan, every deployment alternative that is Prepared, Shared or ContinuouslyMaintained carries a rejection.
Capabilities in PlanningModels Ephemeral Prepared Shared ContinuouslyMaintained
ALL (default from new / builtin) may be selected may be selected may be selected may be selected
ephemeral only (the new test) may be selected rejected rejected rejected

"May be selected" still depends on workload facts and cost evidence.

Caller migration. All existing callers change the same way:

// before
LifecycleInput::new(NOW_MS, SummaryMaintenanceLifecycleCapabilities::default())
    .with_horizon(Horizon(HORIZON_S))
// after
LifecycleInput::new(NOW_MS).with_horizon(Horizon(HORIZON_S))

Updated callers: crates/planner/tests/e2e_plan.rs, crates/planner/tests/summary_sharing.rs, crates/integration-tests/tests/operator_design_examples.rs. crates/planner/src/lib.rs only updates the UserInput.lifecycle doc comment.

Out of scope

Stack and validation

Legacy physical stack: … ← #551 ← #552 ← #553 ← #554 ← #555 ← #556 … · Base: #552 (stack/528-10-univmon) · Next: #554 · Tracker: #528

Closes #525.

Validation:

  • CARGO_TARGET_DIR=/mydata/cargo-target-412 cargo test --locked -p asap-planner
  • CARGO_TARGET_DIR=/mydata/cargo-target-412 cargo test --locked -p asap-integration-tests --test operator_design_examples
  • cargo fmt --all -- --check

🤖 Generated with Claude Code

@zzylol

zzylol commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

Parked as draft: PR priorities changed (see #528). Order is now (A) finish #511 operator sharing, (B) the #572 crate/module reorganization, (C) #509 end-to-end stages. This PR sits on the old stack/528-legacy-physical-base chain, and Phase B moves the files it touches. Its content will be re-scoped onto the new layout in Phase C.

🤖 Generated with Claude Code

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