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 = "lawvere-trace")]
128pub fn plan_transform_passes_parallel_lawvere_layers(
129    requested: &[TransformPassKind],
130) -> omena_lawvere::TransformPassParallelPlanV0 {
131    omena_lawvere::plan_transform_pass_parallel_layers_v0(requested)
132}
133
134pub fn implemented_mutation_pass_ids() -> Vec<&'static str> {
135    default_transform_pass_registry()
136        .entries
137        .into_iter()
138        .filter(|entry| entry.contract.executes_mutation)
139        .map(|entry| entry.contract.id)
140        .collect()
141}
142
143pub fn default_transform_pass_registry() -> TransformPassRegistryV0 {
144    let contracts = default_transform_pass_contracts();
145    let entries = default_transform_pass_descriptors()
146        .into_iter()
147        .filter_map(|descriptor| {
148            contract_for_pass(descriptor.kind, contracts.as_slice())
149                .cloned()
150                .map(|contract| registry_entry_for_descriptor(contract, descriptor))
151        })
152        .collect::<Vec<_>>();
153    TransformPassRegistryV0 {
154        schema_version: "0",
155        product: "omena-transform-passes.pass-registry",
156        entries,
157    }
158}
159
160fn registry_entry_for_descriptor(
161    contract: TransformPassContractV0,
162    descriptor: TransformPassDescriptorV0,
163) -> TransformPassRegistryEntryV0 {
164    let module_family = contract.family;
165    let dispatch_kind = dispatch_kind_for_descriptor(&descriptor);
166    TransformPassRegistryEntryV0 {
167        module_family,
168        query_family: query_family_for_pass(contract.kind),
169        dispatch_kind,
170        execution_status: TransformPassExecutionStatus::RegistryAndPlannerReady,
171        contract,
172        descriptor,
173    }
174}
175
176fn dispatch_kind_for_descriptor(
177    descriptor: &TransformPassDescriptorV0,
178) -> TransformPassDispatchKindV0 {
179    match descriptor.pass_class {
180        TransformPassClassV0::TextLocal => TransformPassDispatchKindV0::TextLocalSliceRewrite,
181        TransformPassClassV0::Structural => TransformPassDispatchKindV0::StructuralIrTransaction,
182        TransformPassClassV0::ModuleEvaluation => {
183            TransformPassDispatchKindV0::ModuleEvaluationHandler
184        }
185        TransformPassClassV0::Emission => TransformPassDispatchKindV0::EmissionBoundary,
186    }
187}
188
189fn query_family_for_pass(kind: TransformPassKind) -> &'static str {
190    match kind.layer() {
191        TransformLayer::SemanticAware => "semantic-aware-transform-query",
192        TransformLayer::Commodity => "commodity-transform-query",
193        TransformLayer::Emission => "emission-transform-query",
194        TransformLayer::SemanticReadOnly => "semantic-read-only-query",
195    }
196}
197
198fn order_passes_by_dag(requested: &[TransformPassKind]) -> Vec<TransformPassKind> {
199    let mut remaining = dedupe_requested_passes(requested);
200    let registry = default_transform_pass_registry();
201    remaining.sort_by_key(|kind| {
202        descriptor_for_pass(*kind, registry.entries.as_slice())
203            .map(|descriptor| (descriptor.phase, descriptor.phase_order, descriptor.id))
204            .unwrap_or((u8::MAX, u16::MAX, ""))
205    });
206
207    let mut ordered = Vec::with_capacity(remaining.len());
208    while !remaining.is_empty() {
209        let next_index = remaining
210            .iter()
211            .position(|candidate| {
212                !has_incoming_edge_from_remaining(
213                    *candidate,
214                    &remaining,
215                    registry.entries.as_slice(),
216                )
217            })
218            .unwrap_or_default();
219        ordered.push(remaining.remove(next_index));
220    }
221
222    ordered
223}
224
225fn dedupe_requested_passes(requested: &[TransformPassKind]) -> Vec<TransformPassKind> {
226    let mut unique = Vec::new();
227    for pass in requested {
228        if !unique.contains(pass) {
229            unique.push(*pass);
230        }
231    }
232    unique
233}
234
235fn conflicting_unordered_pass_pairs(
236    requested: &[TransformPassKind],
237    registry_entries: &[TransformPassRegistryEntryV0],
238    dag_edges: &[TransformDagEdgeV0],
239) -> Vec<TransformPlanPassConflictV0> {
240    let mut conflicts = Vec::new();
241    for (left_index, left) in requested.iter().enumerate() {
242        for right in requested.iter().skip(left_index + 1) {
243            let Some(left_descriptor) = descriptor_for_pass(*left, registry_entries) else {
244                continue;
245            };
246            let Some(right_descriptor) = descriptor_for_pass(*right, registry_entries) else {
247                continue;
248            };
249            let declared = left_descriptor
250                .conflicts_with
251                .contains(&right_descriptor.id)
252                || right_descriptor
253                    .conflicts_with
254                    .contains(&left_descriptor.id);
255            if declared
256                && !dag_path_exists(left_descriptor.id, right_descriptor.id, dag_edges)
257                && !dag_path_exists(right_descriptor.id, left_descriptor.id, dag_edges)
258            {
259                conflicts.push(TransformPlanPassConflictV0 {
260                    pass_a: left_descriptor.id,
261                    pass_b: right_descriptor.id,
262                });
263            }
264        }
265    }
266    conflicts
267}
268
269fn has_incoming_edge_from_remaining(
270    candidate: TransformPassKind,
271    remaining: &[TransformPassKind],
272    registry_entries: &[TransformPassRegistryEntryV0],
273) -> bool {
274    descriptor_for_pass(candidate, registry_entries).is_some_and(|descriptor| {
275        descriptor.depends_on.iter().any(|dependency| {
276            remaining
277                .iter()
278                .any(|other| other.id() == *dependency && *other != candidate)
279        })
280    })
281}
282
283fn edge_applies(edge: &TransformDagEdgeV0, ordered_pass_ids: &[&'static str]) -> bool {
284    ordered_pass_ids.contains(&edge.from) && ordered_pass_ids.contains(&edge.to)
285}
286
287fn edge_is_satisfied(edge: &TransformDagEdgeV0, ordered_pass_ids: &[&'static str]) -> bool {
288    let from = position_of_pass_id(edge.from, ordered_pass_ids);
289    let to = position_of_pass_id(edge.to, ordered_pass_ids);
290    match (from, to) {
291        (Some(from), Some(to)) => from < to,
292        _ => false,
293    }
294}
295
296fn dag_path_exists(from: &'static str, to: &'static str, dag_edges: &[TransformDagEdgeV0]) -> bool {
297    let mut stack = vec![from];
298    let mut visited = Vec::new();
299    while let Some(current) = stack.pop() {
300        if current == to {
301            return true;
302        }
303        if visited.contains(&current) {
304            continue;
305        }
306        visited.push(current);
307        for edge in dag_edges.iter().filter(|edge| edge.from == current) {
308            stack.push(edge.to);
309        }
310    }
311    false
312}
313
314fn position_of_pass_id(pass_id: &'static str, ordered_pass_ids: &[&'static str]) -> Option<usize> {
315    ordered_pass_ids
316        .iter()
317        .position(|ordered_pass_id| *ordered_pass_id == pass_id)
318}
319
320fn pass_is_registered(pass: &TransformPassKind) -> bool {
321    default_transform_pass_registry()
322        .entries
323        .iter()
324        .any(|entry| entry.contract.kind == *pass)
325}
326
327fn contract_for_pass(
328    pass: TransformPassKind,
329    contracts: &[TransformPassContractV0],
330) -> Option<&TransformPassContractV0> {
331    contracts.iter().find(|contract| contract.kind == pass)
332}
333
334fn descriptor_for_pass(
335    pass: TransformPassKind,
336    registry_entries: &[TransformPassRegistryEntryV0],
337) -> Option<&TransformPassDescriptorV0> {
338    registry_entries
339        .iter()
340        .find(|entry| entry.descriptor.kind == pass)
341        .map(|entry| &entry.descriptor)
342}
343
344pub(crate) fn transform_pass_kind_from_id(pass_id: &str) -> Option<TransformPassKind> {
345    all_transform_pass_kinds()
346        .into_iter()
347        .find(|kind| kind.id() == pass_id)
348}