omena_transform_passes/runtime/
planner.rs1use 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(¤t) {
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}