Skip to main content

omena_transform_passes/runtime/
planner.rs

1//! Transform pass registry, DAG planner, and public boundary summary.
2//!
3//! Planner code maps `omena-transform-cst` pass contracts into executable
4//! registry entries, enforces default DAG ordering, and reports the mutation
5//! passes that are implemented by the runtime executor.
6
7use omena_transform_cst::{
8    TRANSFORM_PASS_CATALOG_LEN, TransformDagEdgeV0, TransformLayer, TransformPassClassV0,
9    TransformPassContractV0, TransformPassDescriptorV0, TransformPassKind,
10    all_transform_pass_kinds, default_transform_dag_edges, default_transform_pass_contracts,
11    default_transform_pass_descriptors, transform_build_profile_from_passes,
12};
13
14use crate::{
15    TransformPassDispatchKindV0, TransformPassExecutionStatus, TransformPassPlanV0,
16    TransformPassRegistryEntryV0, TransformPassRegistryV0, TransformPassesBoundarySummaryV0,
17    TransformPlanPassConflictV0,
18};
19
20pub fn summarize_omena_transform_passes_boundary() -> TransformPassesBoundarySummaryV0 {
21    let registry = default_transform_pass_registry();
22    let registry_entries = registry.entries.clone();
23    let pass_count = registry_entries.len();
24    let semantic_aware_pass_count = registry_entries
25        .iter()
26        .filter(|entry| entry.contract.layer == TransformLayer::SemanticAware)
27        .count();
28    let cascade_aware_pass_count = registry_entries
29        .iter()
30        .filter(|entry| entry.contract.reads_cascade_model)
31        .count();
32    let structural_pass_count = registry_entries
33        .iter()
34        .filter(|entry| entry.descriptor.pass_class == TransformPassClassV0::Structural)
35        .count();
36    let text_local_pass_count = registry_entries
37        .iter()
38        .filter(|entry| entry.descriptor.pass_class == TransformPassClassV0::TextLocal)
39        .count();
40    let module_evaluation_pass_count = registry_entries
41        .iter()
42        .filter(|entry| entry.descriptor.pass_class == TransformPassClassV0::ModuleEvaluation)
43        .count();
44
45    TransformPassesBoundarySummaryV0 {
46        schema_version: "0",
47        product: "omena-transform-passes.boundary",
48        registry_entries,
49        dag_edges: default_transform_dag_edges(),
50        pass_count,
51        full_catalog_registered: pass_count == TRANSFORM_PASS_CATALOG_LEN,
52        semantic_aware_pass_count,
53        cascade_aware_pass_count,
54        structural_pass_count,
55        text_local_pass_count,
56        module_evaluation_pass_count,
57        planner_enforces_dag_edges: true,
58        planner_uses_pass_descriptors: true,
59        ordinal_has_execution_semantics: false,
60        execution_runtime_ready: true,
61        incremental_execution_runtime_ready: true,
62        module_evaluation_native_output_marker: "nativeEditOutput",
63        module_evaluation_requires_native_product_output: true,
64        module_evaluation_requires_oracle_readiness: true,
65        module_evaluation_legacy_output_is_oracle_only: true,
66        module_evaluation_preserves_source_without_native_output: true,
67        implemented_mutation_pass_ids: implemented_mutation_pass_ids(),
68        next_surfaces: Vec::new(),
69    }
70}
71
72pub fn plan_transform_passes(requested: &[TransformPassKind]) -> TransformPassPlanV0 {
73    let requested_pass_ids = requested.iter().map(|pass| pass.id()).collect::<Vec<_>>();
74    let registry = default_transform_pass_registry();
75    let dag_edges = default_transform_dag_edges();
76    let requested_unique = dedupe_requested_passes(requested);
77    let conflicting_unordered_pass_pairs = conflicting_unordered_pass_pairs(
78        requested_unique.as_slice(),
79        registry.entries.as_slice(),
80        dag_edges.as_slice(),
81    );
82    let ordered_passes = order_passes_by_dag(requested);
83    let ordered_pass_ids = ordered_passes
84        .iter()
85        .map(|pass| pass.id())
86        .collect::<Vec<_>>();
87    let satisfied_dag_edge_count = dag_edges
88        .iter()
89        .filter(|edge| {
90            edge_applies(edge, &ordered_pass_ids) && edge_is_satisfied(edge, &ordered_pass_ids)
91        })
92        .count();
93    let violated_dag_edge_count = dag_edges
94        .iter()
95        .filter(|edge| {
96            edge_applies(edge, &ordered_pass_ids) && !edge_is_satisfied(edge, &ordered_pass_ids)
97        })
98        .count();
99
100    TransformPassPlanV0 {
101        schema_version: "0",
102        product: "omena-transform-passes.plan",
103        build_profile: transform_build_profile_from_passes(
104            "descriptor-ordered-transform-plan",
105            ordered_passes.as_slice(),
106        ),
107        requested_pass_ids,
108        ordered_pass_ids,
109        satisfied_dag_edge_count,
110        violated_dag_edge_count,
111        all_requested_registered: requested.iter().all(pass_is_registered),
112        conflicting_unordered_pass_pairs,
113    }
114}
115
116pub fn plan_transform_passes_checked(
117    requested: &[TransformPassKind],
118) -> Result<TransformPassPlanV0, TransformPlanPassConflictV0> {
119    let plan = plan_transform_passes(requested);
120    if let Some(conflict) = plan.conflicting_unordered_pass_pairs.first().cloned() {
121        Err(conflict)
122    } else {
123        Ok(plan)
124    }
125}
126
127#[cfg(feature = "transform-catalog-trace")]
128pub fn plan_transform_passes_parallel_transform_catalog_layers(
129    requested: &[TransformPassKind],
130) -> omena_lawvere::TransformCatalogTransformPassParallelPlanV0 {
131    omena_lawvere::plan_transform_catalog_parallel_layers_v0(requested)
132}
133
134#[cfg(feature = "transform-catalog-trace")]
135#[allow(deprecated)]
136#[deprecated(
137    since = "0.4.0",
138    note = "use plan_transform_passes_parallel_transform_catalog_layers; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
139)]
140pub fn plan_transform_passes_parallel_lawvere_layers(
141    requested: &[TransformPassKind],
142) -> omena_lawvere::TransformPassParallelPlanV0 {
143    omena_lawvere::plan_transform_pass_parallel_layers_v0(requested)
144}
145
146pub fn implemented_mutation_pass_ids() -> Vec<&'static str> {
147    default_transform_pass_registry()
148        .entries
149        .into_iter()
150        .filter(|entry| entry.contract.executes_mutation)
151        .map(|entry| entry.contract.id)
152        .collect()
153}
154
155pub fn default_transform_pass_registry() -> TransformPassRegistryV0 {
156    let contracts = default_transform_pass_contracts();
157    let entries = default_transform_pass_descriptors()
158        .into_iter()
159        .filter_map(|descriptor| {
160            contract_for_pass(descriptor.kind, contracts.as_slice())
161                .cloned()
162                .map(|contract| registry_entry_for_descriptor(contract, descriptor))
163        })
164        .collect::<Vec<_>>();
165    TransformPassRegistryV0 {
166        schema_version: "0",
167        product: "omena-transform-passes.pass-registry",
168        entries,
169    }
170}
171
172fn registry_entry_for_descriptor(
173    contract: TransformPassContractV0,
174    descriptor: TransformPassDescriptorV0,
175) -> TransformPassRegistryEntryV0 {
176    let module_family = contract.family;
177    let dispatch_kind = dispatch_kind_for_descriptor(&descriptor);
178    TransformPassRegistryEntryV0 {
179        module_family,
180        query_family: query_family_for_pass(contract.kind),
181        dispatch_kind,
182        execution_status: TransformPassExecutionStatus::RegistryAndPlannerReady,
183        contract,
184        descriptor,
185    }
186}
187
188fn dispatch_kind_for_descriptor(
189    descriptor: &TransformPassDescriptorV0,
190) -> TransformPassDispatchKindV0 {
191    match descriptor.pass_class {
192        TransformPassClassV0::TextLocal => TransformPassDispatchKindV0::TextLocalSliceRewrite,
193        TransformPassClassV0::Structural => TransformPassDispatchKindV0::StructuralIrTransaction,
194        TransformPassClassV0::ModuleEvaluation => {
195            TransformPassDispatchKindV0::ModuleEvaluationHandler
196        }
197        TransformPassClassV0::Emission => TransformPassDispatchKindV0::EmissionBoundary,
198    }
199}
200
201fn query_family_for_pass(kind: TransformPassKind) -> &'static str {
202    match kind.layer() {
203        TransformLayer::SemanticAware => "semantic-aware-transform-query",
204        TransformLayer::Commodity => "commodity-transform-query",
205        TransformLayer::Emission => "emission-transform-query",
206        TransformLayer::SemanticReadOnly => "semantic-read-only-query",
207    }
208}
209
210fn order_passes_by_dag(requested: &[TransformPassKind]) -> Vec<TransformPassKind> {
211    let mut remaining = dedupe_requested_passes(requested);
212    let registry = default_transform_pass_registry();
213    remaining.sort_by_key(|kind| {
214        descriptor_for_pass(*kind, registry.entries.as_slice())
215            .map(|descriptor| (descriptor.phase, descriptor.phase_order, descriptor.id))
216            .unwrap_or((u8::MAX, u16::MAX, ""))
217    });
218
219    let mut ordered = Vec::with_capacity(remaining.len());
220    while !remaining.is_empty() {
221        let next_index = remaining
222            .iter()
223            .position(|candidate| {
224                !has_incoming_edge_from_remaining(
225                    *candidate,
226                    &remaining,
227                    registry.entries.as_slice(),
228                )
229            })
230            .unwrap_or_default();
231        ordered.push(remaining.remove(next_index));
232    }
233
234    ordered
235}
236
237fn dedupe_requested_passes(requested: &[TransformPassKind]) -> Vec<TransformPassKind> {
238    let mut unique = Vec::new();
239    for pass in requested {
240        if !unique.contains(pass) {
241            unique.push(*pass);
242        }
243    }
244    unique
245}
246
247fn conflicting_unordered_pass_pairs(
248    requested: &[TransformPassKind],
249    registry_entries: &[TransformPassRegistryEntryV0],
250    dag_edges: &[TransformDagEdgeV0],
251) -> Vec<TransformPlanPassConflictV0> {
252    let mut conflicts = Vec::new();
253    for (left_index, left) in requested.iter().enumerate() {
254        for right in requested.iter().skip(left_index + 1) {
255            let Some(left_descriptor) = descriptor_for_pass(*left, registry_entries) else {
256                continue;
257            };
258            let Some(right_descriptor) = descriptor_for_pass(*right, registry_entries) else {
259                continue;
260            };
261            let declared = left_descriptor
262                .conflicts_with
263                .contains(&right_descriptor.id)
264                || right_descriptor
265                    .conflicts_with
266                    .contains(&left_descriptor.id);
267            if declared
268                && !dag_path_exists(left_descriptor.id, right_descriptor.id, dag_edges)
269                && !dag_path_exists(right_descriptor.id, left_descriptor.id, dag_edges)
270            {
271                conflicts.push(TransformPlanPassConflictV0 {
272                    pass_a: left_descriptor.id,
273                    pass_b: right_descriptor.id,
274                });
275            }
276        }
277    }
278    conflicts
279}
280
281fn has_incoming_edge_from_remaining(
282    candidate: TransformPassKind,
283    remaining: &[TransformPassKind],
284    registry_entries: &[TransformPassRegistryEntryV0],
285) -> bool {
286    descriptor_for_pass(candidate, registry_entries).is_some_and(|descriptor| {
287        descriptor.depends_on.iter().any(|dependency| {
288            remaining
289                .iter()
290                .any(|other| other.id() == *dependency && *other != candidate)
291        })
292    })
293}
294
295fn edge_applies(edge: &TransformDagEdgeV0, ordered_pass_ids: &[&'static str]) -> bool {
296    ordered_pass_ids.contains(&edge.from) && ordered_pass_ids.contains(&edge.to)
297}
298
299fn edge_is_satisfied(edge: &TransformDagEdgeV0, ordered_pass_ids: &[&'static str]) -> bool {
300    let from = position_of_pass_id(edge.from, ordered_pass_ids);
301    let to = position_of_pass_id(edge.to, ordered_pass_ids);
302    match (from, to) {
303        (Some(from), Some(to)) => from < to,
304        _ => false,
305    }
306}
307
308fn dag_path_exists(from: &'static str, to: &'static str, dag_edges: &[TransformDagEdgeV0]) -> bool {
309    let mut stack = vec![from];
310    let mut visited = Vec::new();
311    while let Some(current) = stack.pop() {
312        if current == to {
313            return true;
314        }
315        if visited.contains(&current) {
316            continue;
317        }
318        visited.push(current);
319        for edge in dag_edges.iter().filter(|edge| edge.from == current) {
320            stack.push(edge.to);
321        }
322    }
323    false
324}
325
326fn position_of_pass_id(pass_id: &'static str, ordered_pass_ids: &[&'static str]) -> Option<usize> {
327    ordered_pass_ids
328        .iter()
329        .position(|ordered_pass_id| *ordered_pass_id == pass_id)
330}
331
332fn pass_is_registered(pass: &TransformPassKind) -> bool {
333    default_transform_pass_registry()
334        .entries
335        .iter()
336        .any(|entry| entry.contract.kind == *pass)
337}
338
339fn contract_for_pass(
340    pass: TransformPassKind,
341    contracts: &[TransformPassContractV0],
342) -> Option<&TransformPassContractV0> {
343    contracts.iter().find(|contract| contract.kind == pass)
344}
345
346fn descriptor_for_pass(
347    pass: TransformPassKind,
348    registry_entries: &[TransformPassRegistryEntryV0],
349) -> Option<&TransformPassDescriptorV0> {
350    registry_entries
351        .iter()
352        .find(|entry| entry.descriptor.kind == pass)
353        .map(|entry| &entry.descriptor)
354}
355
356pub(crate) fn transform_pass_kind_from_id(pass_id: &str) -> Option<TransformPassKind> {
357    all_transform_pass_kinds()
358        .into_iter()
359        .find(|kind| kind.id() == pass_id)
360}