Repository navigation
Conversation
…fetime)
Each physical candidate stores PhysicalProperties { timing, lifetime } with
Lifetime { None, OneEvaluation(Medium), AcrossEvaluations(Medium) };
PhysicalProperties::new rejects (IngestionTime, OneEvaluation). Plan nodes
carry `lifetime` next to their timing; the plan file's node field
`materialization` becomes `lifetime` (wire version 12). compatible_timing
becomes PhysicalProperties::readable_at with the same truth table. Stage 2
offers the same alternatives as before, one to one with the old variants.
Deletes Materialization and its variants, Materialization::timing,
is_materialized_view, compatible_timing, and the old export field.
Part of #1200 (PR 1 of 2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
…e only) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
MaterializationAssignment -> PhysicalPropertiesAssignment, apply_materialization_timings(_to_roots) -> apply_physical_properties(_to_roots), MaterializationCandidate -> KeptOutputCandidate (find_kept_output_candidates), SummaryMaterializationEligibility -> SummaryKeepEligibility, ChooseMaterialization -> ChoosePhysicalProperties, MaterializationConflictsWithFixedTiming -> PhysicalPropertiesConflictWithFixedTiming, is_materialization_point -> is_keepable_state_output, HighestSharedQueryMaterialization -> HighestSharedOneEvaluation, materialize_selected_candidates -> realize_selected_candidates, and test names and files that said materialization / not_materialized. Docs, including planner-layering Algorithm 3 and the Example 1-4 acceptance docs, use (timing, lifetime) for the old enum's values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
zzylol
force-pushed
the
refactor/1200-timing-lifetime
branch
from
November 4, 2026 23:51
5ad3731 to
bca3b77
Compare
…sors - PhysicalPropertiesAssignment maps kept nodes only: with_default and its unassigned field are deleted (tests use kept_at_ingestion_where_allowed), set takes PhysicalProperties, get is private, copy_assignment replaces split_by_timing's get/set pair; Stage 2's choice callback returns an option index instead of Option<Option<_>>. - PhysicalASAPDAGValidationError::InvalidPhysicalProperties carries the typed reason; UnkeptSummaryBuildNotAtQueryTime is its own variant. - Lifetime::is_across_evaluations, OperatorNode::physical_properties and PhysicalASAPDAGNode::physical_properties replace hand-written matches, is_view and kept_at_ingestion. - PhysicalWorkloadPlan::lifetime (a copy of dag's lifetimes) is deleted. - PhysicalProperties gets named consts for the values Stage 2 builds; the Stage 2 constructors and test copies are deleted, as is optimize_physical_asap_assigned (duplicate of the test_support entry). - Code says kept, not materialized, for lifetime != None. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
…est-support test_support::at_ingestion calls the test-support builder PhysicalPropertiesAssignment::kept_at_ingestion_where_allowed, so a crate built alone with logical-asap-optimization/test-support (e.g. cargo test -p asap-cost) needs asap-types' test-support too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
…timed OperatorNode::physical_properties returns Option<Result<..>>: None before timing, Err when timing and lifetime cannot go together. The timing pass reports ExecutionDataStateError::InvalidPhysicalProperties, and execution_demand::computations returns AnalyticalCostError::InvalidPhysicalProperties instead of skipping the node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
…est both timing-pass call sites
AnalyticalCostError::InvalidPhysicalProperties becomes { node, reason }, so a
costing error names the node whose timing and lifetime cannot go together.
New timing-pass tests cover the validate_asap and validate_non_asap call
sites of ExecutionDataStateError::InvalidPhysicalProperties.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
zzylol
commented
Nov 5, 2026
The formulation (#1200) calls l != None materialized. Every name this PR introduced with 'kept' says 'materialized': Lifetime::is_materialized, MaterializedOutputCandidate, find_materialized_output_candidates, SummaryMaterializationEligibility, materialized_at_ingestion_where_allowed, materializes_any, materialized_tumbling_window_chains, PhysicalProperties::MATERIALIZED_IN_MEMORY_* and NOT_MATERIALIZED_AT_*, Consumer::Materialized, and the tests and doc comments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
zzylol
added a commit
that referenced
this pull request
Nov 5, 2026
PhysicalProperties stays the struct of (ExecutionTiming, Lifetime) from #1214. Consumers match (timing(), lifetime()) with one arm per valid state or arms that list their states, and one unreachable!() arm for the invalid (IngestionTime, OneEvaluation): new, readable_at, summary_properties_allowed, run_kind, node_usage, materialized_tumbling_window_chains (now Result, the error names the node), materialized_run_rate (one classification shared by computations and the MILP's context rates) and the devtools phase_label. RunKind::Shareable and ContextKind::Shared become Materialized (kappa_m) and carry no copy of the candidate's properties. The selector and select path share cost_error and materialized_chains_of; Stage 3's window_read_through uses the shared chain predicate. Table tests over every valid state (PhysicalProperties::every_valid) for each consumer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
zzylol
added a commit
that referenced
this pull request
Nov 5, 2026
…e None Review fixes for #1214: the assignment's field, Debug label, set's parameter and docs, error messages, the devtools and viewer labels, the cost detail for chains across evaluations, test names, locals and the fixture name say materialized. post-asap-ir.md documents set(node, properties); the example 4 acceptance doc names materializing(). PhysicalPropertiesAssignment::set with a lifetime None removes the node's assignment in release builds too, instead of only a debug_assert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
…e None Review fixes for #1214: the assignment's field, Debug label, set's parameter and docs, error messages, the devtools and viewer labels, the cost detail for chains across evaluations, test names, locals and the fixture name say materialized. post-asap-ir.md documents set(node, properties); the example 4 acceptance doc names materializing(). PhysicalPropertiesAssignment::set with a lifetime None removes the node's assignment in release builds too, instead of only a debug_assert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
zzylol
force-pushed
the
refactor/1200-timing-lifetime
branch
from
November 5, 2026 20:08
ffd4b72 to
64e7214
Compare
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1200 (PR 1 of 2), tracked in #1207 (step 1a).
Merge unit. This PR merges into main only together with #1207's steps 1a–6 (#1207, Order): the #1200 stack, #1203 2a and 2b, #1201 and #1204 PR 1.
Representation / rules / enumeration
(a) Representation. Each physical candidate stores
PhysicalProperties = (timing, lifetime):timingisIngestionTimeorQueryTime,lifetimeisNone,OneEvaluation(Medium)orAcrossEvaluations(Medium). LifetimeNonemeans the output is not materialized (discarded after its consumer runs). "Materialized" (ℓ ≠ None, Physical properties: replace Materialization with timing × lifetime (None / OneEvaluation / AcrossEvaluations) and a schedule for shareable results, with r^phys and α^phys #1200) is derived (lifetime.is_materialized()), not stored.(IngestionTime, OneEvaluation)cannot be represented:PhysicalProperties::newreturnsInvalidPhysicalProperties::OneEvaluationAtIngestion. The schedule σ (periodic refresh or tumbling-window grid phase of a materialized result) is not part of this PR; it comes in PR 2.(b) Rules. No new rules are added; only the property type changes. Stage 2 (
enumerate_candidate_configurations) still offers, for each operator node of each memo group's candidates:(QueryTime, None);SummaryAgg,SummaryTumblingWindowChain): also(QueryTime, OneEvaluation(Memory));(IngestionTime, AcrossEvaluations(Memory))when some reading query allows ingestion; and, for a tumbling-window chain only,(QueryTime, AcrossEvaluations(Memory))when the deployment keeps query-time results and some consuming query builds one new tumbling window per evaluation;SummaryEstimate,EvaluatePopulation,MaintainPopulation): also(IngestionTime, None); and(QueryTime, OneEvaluation(Memory))when two or more queries may reach it.Then, as before, it prunes
PerEntityAggregateNotExecutable(all alternatives) andRawDataNotRetained(query-timeScan), and Stage 2's later pruning (TooFewQueryReaders,ReachesNoQueryOutput,InputHasNoBuildableProducer) is unchanged. The(old enum value) → (timing, lifetime)mapping is one to one:NotMaterializedat timing τ →(τ, None),MatAtQuery→(QueryTime, OneEvaluation),MatAtQueryKeptAcrossEvaluations→(QueryTime, AcrossEvaluations),MatAtIngestion→(IngestionTime, AcrossEvaluations).(c) Enumeration. Stage 3's MILP picks one physical candidate per used memo group per consumer context, as before. For each consumer input, Stage 2 lists only the producers whose properties a consumer at that timing may read:
PhysicalProperties::readable_at(consumer_timing)is true when the producer has the same timing, or when a query-time consumer reads an ingestion-timeAcrossEvaluationsresult. This replacescompatible_timingwith the same truth table. Stage 3'srun_kindtakesPhysicalProperties:(QueryTime, None)→PerContext,(IngestionTime, None)→Ingestion, materialized (OneEvaluationorAcrossEvaluations) →Shareable(renamedMaterializedin refactor(types, stages): every valid timing × lifetime state explicit; PhysicalRequirement, satisfies_physical, no named states #1216).Why
#1207's formulation gives each physical candidate the properties$p^{\mathrm{phys}}_v = (\tau_v, \ell_v)$ . Before this PR the code folded both into one enum,
Materialization. One variant,MatAtQueryKeptAcrossEvaluations, mixed timing and lifetime, and the timing was also stored a second time next to the enum (PhysicalCandidate::timing,output_state.timing). The two could disagree, and the export had to check them against each other (MaterializationTimingMismatch). Later steps (σ in #1200 PR 2, explicit edges in #1201, the MILP in #1204) read timing and lifetime as separate values.What
asap_types::physical::{Lifetime, PhysicalProperties, InvalidPhysicalProperties}incrates/types/src/physical/properties.rs;Mediummoves there.crates/types/src/physical/materialization.rsis deleted.PhysicalCandidate,PhysicalCandidateKey) holdproperties: PhysicalPropertiesin place oftiming+materialization.OperatorNode,PhysicalASAPDAGNode) holdlifetime: Lifetime; their timing stays where it was (timing/output_state.timing).PhysicalPropertiesAssignment(wasMaterializationAssignment) maps each assigned node to itsPhysicalProperties; a node it does not list is not materialized.PhysicalProperties(NOT_MATERIALIZED_AT_QUERY_TIME,NOT_MATERIALIZED_AT_INGESTION,MATERIALIZED_IN_MEMORY_FOR_ONE_EVALUATION,MATERIALIZED_IN_MEMORY_ACROSS_EVALUATIONS_AT_INGESTION,MATERIALIZED_IN_MEMORY_ACROSS_EVALUATIONS_AT_QUERY_TIME), used by Stage 2 and the tests.ExecutionDataStateError::InvalidPhysicalPropertiesin the timing pass,AnalyticalCostError::InvalidPhysicalProperties { node, reason }inexecution_demand::computations, naming the node), never read as untimed or skipped.Lifetime::is_materialized(ℓ ≠ None) andLifetime::is_across_evaluations(replaced in refactor(types, stages): every valid timing × lifetime state explicit; PhysicalRequirement, satisfies_physical, no named states #1216 byPhysicalProperties::is_materialized, as the doc defines it).PhysicalWorkloadPlan::materializes_any(test support) now readsdag.nodes.OperatorNode::physical_properties,PhysicalASAPDAGNode::physical_properties); code that rebuilt the pair by hand, or re-implementedreadable_at, uses them.materializationbecomeslifetime("None",{"OneEvaluation": "Memory"},{"AcrossEvaluations": "Memory"}).INSTALLABLE_PLAN_WIRE_VERSIONgoes from 11 to 12. The executor, the DAG viewer (app.js,stages.js,memo.js), fixtures and snapshots all change in this PR. No consumer of the old format is kept.asap-cost), Stage 3 (groups.rs, MILP cost terms, decode), materialized views, the executor's physical planner, devtools and docs match on(timing, lifetime).Deleted:
Materializationand its variantsNotMaterialized,MatAtIngestion,MatAtQuery,MatAtQueryKeptAcrossEvaluations;Materialization::timing();Materialization::is_materialized_viewandtest_support::is_materialized_view;compatible_timing;OperatorNode::materializationandwith_materialization;PhysicalCandidate::{timing, materialization};PhysicalCandidateKey::{timing, materialization};PhysicalASAPDAGNode::materialization;PhysicalWorkloadPlan::materialization(a copy of thedagnodes' lifetimes; consumers readdag.nodes), and its filling code in Stage 2;PhysicalASAPDAGValidationError::MaterializationTimingMismatch(split intoInvalidPhysicalProperties { node, reason }andUnmaterializedSummaryBuildNotAtQueryTime { node });MaterializationAssignment::with_default, itsunassignedfield and the branch reading it (only tests used it; they now call the test-support buildermaterialized_at_ingestion_where_allowed); explicit not-materialized assignments (set(node, None); #1216 letssettake any validPhysicalProperties) and theOption<Option<_>>they needed inget(now private) and in Stage 2's choice callback (now an option index);timing.rs'skept_at_ingestion(re-implementedreadable_at);materialized_view.rs'sis_view(nowLifetime::is_across_evaluations); Stage 2'skept_for_one_evaluation(),kept_across_evaluations_at_ingestion(),kept_across_evaluations_at_query_time(),physical_properties(t, l)and the tests' copies (now associated consts);optimize_physical_asap_assigned(a duplicate oftest_support::optimize_physical_asap_chosen); the unusedSerializederive onPhysicalProperties; the tests of removed behavior (query_time_state_moves_its_consuming_summary_to_query_time,explicit_not_kept_keeps_a_fixed_timing); the integration tests' copy of the old enum,MaterializationOption { MatAtIngestion, MatAtQuery, NotMaterialized }(nowMaterializing { MaterializedAtIngestion, MaterializedAtQueryTime, NotMaterialized }, a test-only projection without lifetime length or medium).Renamed (names that held the old enum's values now say what they hold; code, tests and docs):
MaterializationAssignmentPhysicalPropertiesAssignmentapply_materialization_timings,apply_materialization_timings_to_rootsapply_physical_properties,apply_physical_properties_to_rootsMaterializationCandidate(Stage 2, a node whose output may be materialized),materialization_candidates()MaterializedOutputCandidate,find_materialized_output_candidates()ChooseMaterialization,materialization_assignmentsChoosePhysicalProperties,physical_properties_assignmentsExecutionDataStateError::MaterializationConflictsWithFixedTimingPhysicalPropertiesConflictWithFixedTimingis_materialization_pointis_materializable_state_outputReaderConstraint::HighestSharedQueryMaterializationHighestSharedOneEvaluationmaterialize_selected_candidatesrealize_selected_candidatessummary_mode_allowed(mode)summary_properties_allowed(candidate_properties)MaterializationOption { MatAtIngestion, MatAtQuery, NotMaterialized },materialization()Materializing { MaterializedAtIngestion, MaterializedAtQueryTime, NotMaterialized },materializing()tests/materialization_e2e.rs; tests and helpers named*_materialization*,*not_materialized*,*unmaterialized*tests/physical_properties_e2e.rs;*_physical_properties*/*_lifetime*,*not_materialized*Docs (design proposals including
planner-layering.mdAlgorithm 3 and its legend, the Example 1–4 acceptance docs,joint-planning-search.md,window-materialization-choices.md,maintained-populations.md, develop docs) use(timing, lifetime)in place ofNotMaterialized/MatAtQuery/MatAtIngestion/MatAtQueryKeptAcrossEvaluations, and "physical properties assignment" / "materialized-output candidate" for the renamed types. Code says "materialized" for ℓ ≠ None, as #1200 does; "materialized view" (a node materialized across evaluations) and the SQL frontend's unrelatedmaterializeare unchanged.Considered, not done: storing one
PhysicalPropertiesfield on plan nodes instead oftiming+lifetime(a larger wire-shape change); the accessors above give the pair without it.Formulation notation → code
ExecutionTiming(PhysicalProperties::timing)Lifetime::{None, OneEvaluation(Medium), AcrossEvaluations(Medium)}PhysicalProperties(struct of the two, checked bynew)Lifetime::is_materialized,MaterializedOutputCandidate,materialized_tumbling_window_chains,PhysicalProperties::MATERIALIZED_IN_MEMORY_*,NOT_MATERIALIZED_AT_*InvalidPhysicalProperties::OneEvaluationAtIngestionPhysicalProperties::readable_atNaming (user decision, 2026-11-05). An earlier version of this PR said "kept" for ℓ ≠ None. The formulation's word is "materialized", so every name this PR introduced says "materialized" (commit "say materialized, not kept"). Prose that describes a lifetime ("kept across evaluations", "kept until every consumer of one evaluation finishes") follows #1200's own definitions of the lifetimes.
Concepts not in the formulation:
Medium(Memory / Disk), carried insideLifetimeas decided; the run kindsPerContext/Ingestion(named in #1200'sRunKindformula).New abstractions not in #1207 or the doc: decisions (2026-11-05)
Per #1207's interface policy, these names are not defined by #1207, #1200 or section 3 of
memo-groups-properties-requirements.md. The user decided each one on 2026-11-05. #1207's steps 1a–6 merge into main as one merge unit (#1207, Order). So an interim abstraction may stay in this PR only when a later PR of the unit deletes it; none reaches main.PhysicalProperties::{NOT_MATERIALIZED_AT_QUERY_TIME, NOT_MATERIALIZED_AT_INGESTION, MATERIALIZED_IN_MEMORY_FOR_ONE_EVALUATION, MATERIALIZED_IN_MEMORY_ACROSS_EVALUATIONS_AT_INGESTION, MATERIALIZED_IN_MEMORY_ACROSS_EVALUATIONS_AT_QUERY_TIME}PhysicalProperties::new(τ, ℓ); nonot_materialized_ateitherPhysicalPropertiesAssignment(wasMaterializationAssignment),set,copy_assignment(new),apply_physical_properties[_to_roots](renamed)OperatorNodeDAG the realizer and the export readPhysicalCandidate/DagOfSelectedCandidates(doc, "Realizer and export")MaterializedOutputCandidate,find_materialized_output_candidates,ChoosePhysicalProperties,physical_properties_assignments,realize_selected_candidates,summary_properties_allowed,is_materializable_state_output,materialized_tumbling_window_chains,ReaderConstraint::HighestSharedOneEvaluationMaterializationMaterializedOutputCandidate::options() -> Vec<Option<PhysicalProperties>>None= no entry: the timing pass places a not-materialized node where its consumer runsPhysicalASAPDAGNode::physical_properties()(timing, lifetime)as a checked pairAnalyticalCostError::InvalidPhysicalProperties { node, reason },PhysicalASAPDAGValidationError::InvalidPhysicalProperties { node, reason }PhysicalASAPDAGValidationError::InvalidPhysicalProperties { node, reason }carrying the definedInvalidPhysicalProperties; cost wraps it asAnalyticalCostError::InvalidPhysicalDag(_)PhysicalASAPDAGValidationError::UnmaterializedSummaryBuildNotAtQueryTimesatisfies_physicalfalse)materialized_at_ingestion_where_allowed(test-support)MaterializationAssignment::with_defaultand the privateall_at_ingestionforces_query_time,is_filtered_build), and 8 test sites plusvalidate_at_ingestion/planned_data_stateuse itReplaced later in this stack by the defined interface (#1216, decided 2026-11-05):
PhysicalProperties::readable_at(consumer_timing)→PhysicalRequirement { consumer_timing, reads_from: Evaluation, consumer_schedule: Option<EvaluationSchedule> }+satisfies_physical(consumer_scheduleisNoneuntil #1200 PR 2);Lifetime::is_materialized→PhysicalProperties::is_materialized;Lifetime::is_across_evaluationsdeleted. They are done in #1216 rather than here so the three stacked PRs do not each rewrite the same call sites.Divergences from the doc's section 3: no
schedulefield (#1200 PR 2);ExecutionTimingstays inir/properties/execution_data_state.rs(the doc places it inphysical/properties.rs); plan nodes keeptiming+lifetimefields (the export writes both, as the doc says).How
PhysicalPropertiesis a pair with private fields and a checked constructor, so every value in Stage 2 and Stage 3 is a valid row of the table inproperties.rs. Stage 2 builds its alternatives fromPhysicalProperties' named consts, each one of the old enum's values. Code that matched on an old variant now matches on(timing, lifetime)with the mapping in (b). The plan's DAG validation rejects a node whose timing and lifetime cannot go together, or a summary build that is not materialized outside query time, which is what the old timing check accepted.Key code interfaces added / modified
crates/types/src/physical/properties.rs(new; replacesmaterialization.rs)crates/types/src/stages/physical/memo_candidates.rscrates/types/src/ir/operator/node.rs,crates/types/src/ir/physical_export.rs,crates/types/src/physical/workload_plan.rs,crates/types/src/ir/properties/timing.rs,crates/planner/src/installable.rsUsage (end to end, run at the head commit): Example 1's two PromQL queries,
sum by (job) (rate(http_requests_total[1m]))andtopk by (job) (10, sum_over_time(http_requests_total[1m])), exact, every 10 s:# The workload file the user writes; the planner runs Stages 0-3 on it and prints each stage. cargo run --release -p asap-devtools --bin asap_planner -- \ --workload tools/dag-viewer/workloads/planner-layering/example1.json --run-until stage3Stage 2's output lists each physical candidate with its
PhysicalPropertiesand its readable producers (readable_at). The scanO0gets three candidates; an exactSummaryAggthat may run at ingestion gets(IngestionTime, AcrossEvaluations)and reads only the ingestion-time scanC1:Pruned candidates name their properties too (
G4 O155 QueryTime OneEvaluation(Memory) TooFewQueryReaders). Stage 3 selects the shared query-time time rangeC4(OneEvaluation) read by both queries, withcost_per_second: 4.377000.Major algorithm changes
None. Candidate generation,
readable_at(wascompatible_timing),run_kindand the cost terms keep the same truth tables under the mapping in (b). One cost arm changes for a case Stage 2 does not generate (see Verification).Before this PR
A candidate's timing was stored twice (
PhysicalCandidate::timingand insideMaterialization), and lifetime only existed as part of four enum variants. Code asking "is this kept across evaluations?" had to listMatAtIngestion | MatAtQueryKeptAcrossEvaluations; code asking "when does it run?" calledMaterialization::timing(). The plan file had amaterializationfield per node.After this PR
Timing and lifetime are separate values, and only valid pairs exist. The next steps of #1207 (σ in #1200 PR 2, explicit edges in #1201, MILP contexts only for
Nonecandidates in #1204) can each read the value they need. The plan file has alifetimefield per node at wire version 12; older plan files are refused by version.Verification
cargo check --workspace --all-targets,cargo fmt --all,cargo clippy --workspace --all-targets -- -D warnings: clean.cargo test --workspace --no-fail-fastat the head (covers types, cost, physical-asap-optimization, plan-selection--lib, logical-asap-optimization, planner, executor, devtools snapshots, integration tests): 2093 passed, 0 failed, 40 ignored. An earlier run caughtdeployment_capabilities::query_time_results_retained_still_offers_the_chain_shared_within_one_evaluation: its test helper matched everyAcrossEvaluationschain, ingestion-time ones included; it now matches(QueryTime, AcrossEvaluations).logical-asap-optimization'stest-supportfeature now enablesasap-types/test-support(itsat_ingestionhelper calls the test-support builder), socargo test -p asap-costalone builds;cargo check -p <crate> --all-targetspasses for every touched crate on its own.cargo test -p asap-types(256 passed),-p asap-cost(45),-p asap-plan-selection --lib(142),-p asap-planner --test planner runtime_install(37), all 0 failed; new testsan_invalid_timed_node_reports_its_error,an_invalid_node_is_reported_not_skipped.AnalyticalCostError::InvalidPhysicalPropertiesnames its node; new timing-pass testsa_summary_build_over_an_invalid_input_reports_its_error(thevalidate_asapcall site) anda_non_asap_node_over_an_invalid_input_reports_its_error(thevalidate_non_asapcall site). After the kept → materialized rename:cargo test -p asap-types,-p asap-cost,-p asap-physical-asap-optimization,-p asap-executor,-p asap-planner,-p asap-plan-selection --lib,-p asap-devtools --lib,-p asap-devtools --test devtools,-p asap-integration-tests: types 258, cost 45, physical-asap-optimization 29, executor 296, planner 89 (8 wall-clock ignored), plan-selection lib 142, devtools lib 18, devtools tests 82, integration tests 364 passed, 0 failed;cargo fmt --allandcargo clippy --workspace --all-targets -- -D warnings: clean.64e721439): the remaining review fixes (kept → materialized in fields, errors, labels, test names and docs;setwith lifetimeNoneremoves the entry in release builds too).cargo test -p asap-types(259),-p asap-cost(45),-p asap-physical-asap-optimization(29),-p asap-executor(296),-p asap-planner(89, 8 wall-clock ignored),-p asap-plan-selection --lib,-p asap-devtools,-p asap-integration-tests(364): 0 failed afterrange_evaluation::stage3_plan_selection_prices_61_evaluations_per_dashboard_requestwas updated for the chain's detail text "; materializes N bytes across evaluations" (was "; keeps N bytes across evaluations");cargo fmtandcargo clippy --workspace --all-targets -- -D warnings: clean.node --test tools/dag-viewer/{stages,enumeration,memo}.test.mjs: 43 pass."materialization": "NotMaterialized"→"lifetime": "None",{"MatAtIngestion": "Memory"}→{"AcrossEvaluations": "Memory"},{"MatAtQuery": "Memory"}→{"OneEvaluation": "Memory"}. No selected plan or cost changes in any snapshot.asap_cost::usage::node_usage: a query-timeAcrossEvaluationsnode that is not a tumbling-window chain is now priced as a whole materialized state (state × 2, as an ingestion-time materialized state), not with the chain's per-window retention formula. Stage 2 offers query-timeAcrossEvaluationsonly to tumbling-window chains, so no generated plan reaches this arm; the plan-selection test helperkeeping_statedoes.maintained(the per-sample update charge) now requires ingestion timing as well asAcrossEvaluations(Memory). Before, it keyed onMatAtIngestion(Memory), which always meant ingestion timing.main(plans are checked by hand withasap_planner). Wall-clock / time-budget tests stay ignored.🤖 Generated with Claude Code
https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ