1use 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 TransformPassPlanningErrorV0, TransformPassRegistryEntryV0, TransformPassRegistryV0,
17 TransformPassesBoundarySummaryV0, TransformPlanDependencyCycleV0,
18 TransformPlanDependencyEdgeV0, TransformPlanPassConflictV0,
19};
20
21pub fn summarize_omena_transform_passes_boundary() -> TransformPassesBoundarySummaryV0 {
22 let registry = default_transform_pass_registry();
23 let registry_entries = registry.entries.clone();
24 let pass_count = registry_entries.len();
25 let semantic_aware_pass_count = registry_entries
26 .iter()
27 .filter(|entry| entry.contract.layer == TransformLayer::SemanticAware)
28 .count();
29 let cascade_aware_pass_count = registry_entries
30 .iter()
31 .filter(|entry| entry.contract.reads_cascade_model)
32 .count();
33 let structural_pass_count = registry_entries
34 .iter()
35 .filter(|entry| entry.descriptor.pass_class == TransformPassClassV0::Structural)
36 .count();
37 let text_local_pass_count = registry_entries
38 .iter()
39 .filter(|entry| entry.descriptor.pass_class == TransformPassClassV0::TextLocal)
40 .count();
41 let module_evaluation_pass_count = registry_entries
42 .iter()
43 .filter(|entry| entry.descriptor.pass_class == TransformPassClassV0::ModuleEvaluation)
44 .count();
45
46 TransformPassesBoundarySummaryV0 {
47 schema_version: "0",
48 product: "omena-transform-passes.boundary",
49 registry_entries,
50 dag_edges: default_transform_dag_edges(),
51 pass_count,
52 full_catalog_registered: pass_count == TRANSFORM_PASS_CATALOG_LEN,
53 semantic_aware_pass_count,
54 cascade_aware_pass_count,
55 structural_pass_count,
56 text_local_pass_count,
57 module_evaluation_pass_count,
58 planner_enforces_dag_edges: true,
59 planner_uses_pass_descriptors: true,
60 ordinal_has_execution_semantics: false,
61 execution_runtime_ready: true,
62 incremental_execution_runtime_ready: true,
63 module_evaluation_native_output_marker: "nativeEditOutput",
64 module_evaluation_requires_native_product_output: true,
65 module_evaluation_requires_oracle_readiness: true,
66 module_evaluation_legacy_output_is_oracle_only: true,
67 module_evaluation_preserves_source_without_native_output: true,
68 implemented_mutation_pass_ids: implemented_mutation_pass_ids(),
69 next_surfaces: Vec::new(),
70 }
71}
72
73#[allow(clippy::panic)]
77pub fn plan_transform_passes(requested: &[TransformPassKind]) -> TransformPassPlanV0 {
78 let registry = default_transform_pass_registry();
79 let dag_edges = default_transform_dag_edges();
80 plan_transform_passes_with_registry(requested, ®istry, dag_edges.as_slice()).unwrap_or_else(
81 |cycle| panic!("default transform pass registry contains a dependency cycle: {cycle:?}"),
82 )
83}
84
85fn plan_transform_passes_with_registry(
86 requested: &[TransformPassKind],
87 registry: &TransformPassRegistryV0,
88 dag_edges: &[TransformDagEdgeV0],
89) -> Result<TransformPassPlanV0, TransformPlanDependencyCycleV0> {
90 let requested_pass_ids = requested.iter().map(|pass| pass.id()).collect::<Vec<_>>();
91 let requested_unique = dedupe_requested_passes(requested);
92 let conflicting_unordered_pass_pairs = conflicting_unordered_pass_pairs(
93 requested_unique.as_slice(),
94 registry.entries.as_slice(),
95 dag_edges,
96 );
97 let ordered_passes = order_passes_by_registry(requested, registry.entries.as_slice())?;
98 let ordered_pass_ids = ordered_passes
99 .iter()
100 .map(|pass| pass.id())
101 .collect::<Vec<_>>();
102 let satisfied_dag_edge_count = dag_edges
103 .iter()
104 .filter(|edge| {
105 edge_applies(edge, &ordered_pass_ids) && edge_is_satisfied(edge, &ordered_pass_ids)
106 })
107 .count();
108 let violated_dag_edge_count = dag_edges
109 .iter()
110 .filter(|edge| {
111 edge_applies(edge, &ordered_pass_ids) && !edge_is_satisfied(edge, &ordered_pass_ids)
112 })
113 .count();
114
115 Ok(TransformPassPlanV0 {
116 schema_version: "0",
117 product: "omena-transform-passes.plan",
118 build_profile: transform_build_profile_from_passes(
119 "descriptor-ordered-transform-plan",
120 ordered_passes.as_slice(),
121 ),
122 requested_pass_ids,
123 ordered_pass_ids,
124 satisfied_dag_edge_count,
125 violated_dag_edge_count,
126 all_requested_registered: requested
127 .iter()
128 .all(|pass| descriptor_for_pass(*pass, registry.entries.as_slice()).is_some()),
129 conflicting_unordered_pass_pairs,
130 })
131}
132
133pub fn plan_transform_passes_checked(
134 requested: &[TransformPassKind],
135) -> Result<TransformPassPlanV0, TransformPassPlanningErrorV0> {
136 let registry = default_transform_pass_registry();
137 let dag_edges = default_transform_dag_edges();
138 plan_transform_passes_checked_with_registry(requested, ®istry, dag_edges.as_slice())
139}
140
141fn plan_transform_passes_checked_with_registry(
142 requested: &[TransformPassKind],
143 registry: &TransformPassRegistryV0,
144 dag_edges: &[TransformDagEdgeV0],
145) -> Result<TransformPassPlanV0, TransformPassPlanningErrorV0> {
146 let plan = plan_transform_passes_with_registry(requested, registry, dag_edges)
147 .map_err(|cycle| TransformPassPlanningErrorV0::DependencyCycle { cycle })?;
148 if let Some(conflict) = plan.conflicting_unordered_pass_pairs.first().cloned() {
149 Err(TransformPassPlanningErrorV0::UnorderedPassConflict { conflict })
150 } else {
151 Ok(plan)
152 }
153}
154
155#[cfg(feature = "transform-catalog-trace")]
156pub fn plan_transform_passes_parallel_transform_catalog_layers(
157 requested: &[TransformPassKind],
158) -> omena_lawvere::TransformCatalogTransformPassParallelPlanV0 {
159 omena_lawvere::plan_transform_catalog_parallel_layers_v0(requested)
160}
161
162#[cfg(feature = "transform-catalog-trace")]
163#[allow(deprecated)]
164#[deprecated(
165 since = "0.4.0",
166 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"
167)]
168pub fn plan_transform_passes_parallel_lawvere_layers(
169 requested: &[TransformPassKind],
170) -> omena_lawvere::TransformPassParallelPlanV0 {
171 omena_lawvere::plan_transform_pass_parallel_layers_v0(requested)
172}
173
174pub fn implemented_mutation_pass_ids() -> Vec<&'static str> {
175 default_transform_pass_registry()
176 .entries
177 .into_iter()
178 .filter(|entry| entry.contract.executes_mutation)
179 .map(|entry| entry.contract.id)
180 .collect()
181}
182
183pub fn default_transform_pass_registry() -> TransformPassRegistryV0 {
184 let contracts = default_transform_pass_contracts();
185 let entries = default_transform_pass_descriptors()
186 .into_iter()
187 .filter_map(|descriptor| {
188 contract_for_pass(descriptor.kind, contracts.as_slice())
189 .cloned()
190 .map(|contract| registry_entry_for_descriptor(contract, descriptor))
191 })
192 .collect::<Vec<_>>();
193 TransformPassRegistryV0 {
194 schema_version: "0",
195 product: "omena-transform-passes.pass-registry",
196 entries,
197 }
198}
199
200fn registry_entry_for_descriptor(
201 contract: TransformPassContractV0,
202 descriptor: TransformPassDescriptorV0,
203) -> TransformPassRegistryEntryV0 {
204 let module_family = contract.family;
205 let dispatch_kind = dispatch_kind_for_descriptor(&descriptor);
206 TransformPassRegistryEntryV0 {
207 module_family,
208 query_family: query_family_for_pass(contract.kind),
209 dispatch_kind,
210 execution_status: TransformPassExecutionStatus::RegistryAndPlannerReady,
211 contract,
212 descriptor,
213 }
214}
215
216fn dispatch_kind_for_descriptor(
217 descriptor: &TransformPassDescriptorV0,
218) -> TransformPassDispatchKindV0 {
219 match descriptor.pass_class {
220 TransformPassClassV0::TextLocal => TransformPassDispatchKindV0::TextLocalSliceRewrite,
221 TransformPassClassV0::Structural => TransformPassDispatchKindV0::StructuralIrTransaction,
222 TransformPassClassV0::ModuleEvaluation => {
223 TransformPassDispatchKindV0::ModuleEvaluationHandler
224 }
225 TransformPassClassV0::Emission => TransformPassDispatchKindV0::EmissionBoundary,
226 }
227}
228
229fn query_family_for_pass(kind: TransformPassKind) -> &'static str {
230 match kind.layer() {
231 TransformLayer::SemanticAware => "semantic-aware-transform-query",
232 TransformLayer::Commodity => "commodity-transform-query",
233 TransformLayer::Emission => "emission-transform-query",
234 TransformLayer::SemanticReadOnly => "semantic-read-only-query",
235 }
236}
237
238#[allow(clippy::expect_used)]
241fn order_passes_by_registry(
242 requested: &[TransformPassKind],
243 registry_entries: &[TransformPassRegistryEntryV0],
244) -> Result<Vec<TransformPassKind>, TransformPlanDependencyCycleV0> {
245 let mut remaining = dedupe_requested_passes(requested);
246 remaining.sort_by_key(|kind| {
247 descriptor_for_pass(*kind, registry_entries)
248 .map(|descriptor| (descriptor.phase, descriptor.phase_order, descriptor.id))
249 .unwrap_or((u8::MAX, u16::MAX, ""))
250 });
251
252 let mut ordered = Vec::with_capacity(remaining.len());
253 while !remaining.is_empty() {
254 let Some(next_index) = remaining.iter().position(|candidate| {
255 !has_incoming_edge_from_remaining(*candidate, &remaining, registry_entries)
256 }) else {
257 let cycle = dependency_cycle_witness(remaining.as_slice(), registry_entries)
258 .expect("Kahn planner no-progress state must contain a dependency cycle");
259 return Err(cycle);
260 };
261 ordered.push(remaining.remove(next_index));
262 }
263
264 Ok(ordered)
265}
266
267#[cfg(test)]
268#[allow(clippy::expect_used, clippy::items_after_test_module, clippy::panic)]
269mod planner_cycle_tests {
270 use super::*;
271
272 #[test]
273 fn constructed_dependency_cycle_is_not_silently_ordered() {
274 let mut registry = default_transform_pass_registry();
275 registry
276 .entries
277 .iter_mut()
278 .find(|entry| entry.descriptor.kind == TransformPassKind::ImportInline)
279 .expect("import-inline descriptor")
280 .descriptor
281 .depends_on = vec![TransformPassKind::PrintCss.id()];
282 registry
283 .entries
284 .iter_mut()
285 .find(|entry| entry.descriptor.kind == TransformPassKind::PrintCss)
286 .expect("print-css descriptor")
287 .descriptor
288 .depends_on = vec![TransformPassKind::ImportInline.id()];
289
290 let error = plan_transform_passes_checked_with_registry(
291 &[TransformPassKind::ImportInline, TransformPassKind::PrintCss],
292 ®istry,
293 default_transform_dag_edges().as_slice(),
294 )
295 .expect_err("a cyclic transform registry must not produce an arbitrary order");
296 let serialized = serde_json::to_value(&error).expect("serialize typed planner error");
297 eprintln!("TRANSFORM_PLANNER_CYCLE_ERROR={serialized}");
298 assert_eq!(serialized["kind"], "dependencyCycle");
299 assert_eq!(
300 serialized["cycle"]["dependencyEdges"]
301 .as_array()
302 .map(Vec::len),
303 Some(2)
304 );
305 let TransformPassPlanningErrorV0::DependencyCycle { cycle: error } = error else {
306 panic!("constructed dependency cycle returned the wrong typed planning error");
307 };
308
309 assert_eq!(
310 error.cycle_path,
311 vec!["import-inline", "print-css", "import-inline"]
312 );
313 assert_eq!(
314 error.dependency_edges,
315 vec![
316 TransformPlanDependencyEdgeV0 {
317 prerequisite_pass_id: "print-css",
318 dependent_pass_id: "import-inline",
319 },
320 TransformPlanDependencyEdgeV0 {
321 prerequisite_pass_id: "import-inline",
322 dependent_pass_id: "print-css",
323 },
324 ]
325 );
326 }
327
328 #[test]
329 fn default_catalog_has_no_dependency_cycle_across_300k_deterministic_requests() {
330 const PROBE_INPUT_COUNT: usize = 300_000;
331 const MAX_REQUEST_WIDTH: usize = 8;
332
333 let registry = default_transform_pass_registry();
334 let dag_edges = default_transform_dag_edges();
335 let catalog = all_transform_pass_kinds();
336 let mut state = 0x9e37_79b9_7f4a_7c15_u64;
337 let mut observed_pass_ids = std::collections::BTreeSet::new();
338 let mut cycle_error_count = 0_usize;
339 let mut violated_dag_edge_plan_count = 0_usize;
340
341 for _ in 0..PROBE_INPUT_COUNT {
342 state ^= state << 13;
343 state ^= state >> 7;
344 state ^= state << 17;
345 let width = 1 + (state as usize % MAX_REQUEST_WIDTH);
346 let mut requested = Vec::with_capacity(width);
347 for _ in 0..width {
348 state ^= state << 13;
349 state ^= state >> 7;
350 state ^= state << 17;
351 let pass = catalog[state as usize % catalog.len()];
352 observed_pass_ids.insert(pass.id());
353 requested.push(pass);
354 }
355 match plan_transform_passes_with_registry(
356 requested.as_slice(),
357 ®istry,
358 dag_edges.as_slice(),
359 ) {
360 Ok(plan) => {
361 violated_dag_edge_plan_count += usize::from(plan.violated_dag_edge_count > 0);
362 }
363 Err(_) => cycle_error_count += 1,
364 }
365 }
366
367 eprintln!(
368 "TRANSFORM_PLANNER_PROBE={{\"inputCount\":{PROBE_INPUT_COUNT},\"maximumRequestWidth\":{MAX_REQUEST_WIDTH},\"observedPassKindCount\":{},\"cycleErrorCount\":{cycle_error_count},\"violatedDagEdgePlanCount\":{violated_dag_edge_plan_count}}}",
369 observed_pass_ids.len(),
370 );
371 assert_eq!(observed_pass_ids.len(), catalog.len());
372 assert_eq!(cycle_error_count, 0);
373 assert_eq!(violated_dag_edge_plan_count, 0);
374 }
375}
376
377fn dependency_cycle_witness(
378 remaining: &[TransformPassKind],
379 registry_entries: &[TransformPassRegistryEntryV0],
380) -> Option<TransformPlanDependencyCycleV0> {
381 let remaining_ids = remaining.iter().map(|pass| pass.id()).collect::<Vec<_>>();
382 let mut visit_state = std::collections::BTreeMap::<&'static str, u8>::new();
383 let mut stack = Vec::new();
384 for pass_id in &remaining_ids {
385 if visit_state.get(pass_id).copied().unwrap_or_default() == 0
386 && let Some(cycle) = visit_dependency_cycle(
387 pass_id,
388 remaining_ids.as_slice(),
389 registry_entries,
390 &mut visit_state,
391 &mut stack,
392 )
393 {
394 return Some(cycle);
395 }
396 }
397 None
398}
399
400fn visit_dependency_cycle(
401 pass_id: &'static str,
402 remaining_ids: &[&'static str],
403 registry_entries: &[TransformPassRegistryEntryV0],
404 visit_state: &mut std::collections::BTreeMap<&'static str, u8>,
405 stack: &mut Vec<&'static str>,
406) -> Option<TransformPlanDependencyCycleV0> {
407 visit_state.insert(pass_id, 1);
408 stack.push(pass_id);
409 let mut dependencies = registry_entries
410 .iter()
411 .find(|entry| entry.descriptor.id == pass_id)
412 .map(|entry| entry.descriptor.depends_on.clone())
413 .unwrap_or_default();
414 dependencies.sort_unstable();
415 dependencies.dedup();
416 for dependency in dependencies
417 .into_iter()
418 .filter(|dependency| remaining_ids.contains(dependency))
419 {
420 match visit_state.get(dependency).copied().unwrap_or_default() {
421 0 => {
422 if let Some(cycle) = visit_dependency_cycle(
423 dependency,
424 remaining_ids,
425 registry_entries,
426 visit_state,
427 stack,
428 ) {
429 return Some(cycle);
430 }
431 }
432 1 => {
433 let cycle_start = stack
434 .iter()
435 .position(|candidate| *candidate == dependency)?;
436 let mut cycle_path = stack[cycle_start..].to_vec();
437 cycle_path.push(dependency);
438 let dependency_edges = cycle_path
439 .windows(2)
440 .map(|pair| TransformPlanDependencyEdgeV0 {
441 prerequisite_pass_id: pair[1],
442 dependent_pass_id: pair[0],
443 })
444 .collect();
445 return Some(TransformPlanDependencyCycleV0 {
446 cycle_path,
447 dependency_edges,
448 });
449 }
450 _ => {}
451 }
452 }
453 stack.pop();
454 visit_state.insert(pass_id, 2);
455 None
456}
457
458fn dedupe_requested_passes(requested: &[TransformPassKind]) -> Vec<TransformPassKind> {
459 let mut unique = Vec::new();
460 for pass in requested {
461 if !unique.contains(pass) {
462 unique.push(*pass);
463 }
464 }
465 unique
466}
467
468fn conflicting_unordered_pass_pairs(
469 requested: &[TransformPassKind],
470 registry_entries: &[TransformPassRegistryEntryV0],
471 dag_edges: &[TransformDagEdgeV0],
472) -> Vec<TransformPlanPassConflictV0> {
473 let mut conflicts = Vec::new();
474 for (left_index, left) in requested.iter().enumerate() {
475 for right in requested.iter().skip(left_index + 1) {
476 let Some(left_descriptor) = descriptor_for_pass(*left, registry_entries) else {
477 continue;
478 };
479 let Some(right_descriptor) = descriptor_for_pass(*right, registry_entries) else {
480 continue;
481 };
482 let declared = left_descriptor
483 .conflicts_with
484 .contains(&right_descriptor.id)
485 || right_descriptor
486 .conflicts_with
487 .contains(&left_descriptor.id);
488 if declared
489 && !dag_path_exists(left_descriptor.id, right_descriptor.id, dag_edges)
490 && !dag_path_exists(right_descriptor.id, left_descriptor.id, dag_edges)
491 {
492 conflicts.push(TransformPlanPassConflictV0 {
493 pass_a: left_descriptor.id,
494 pass_b: right_descriptor.id,
495 });
496 }
497 }
498 }
499 conflicts
500}
501
502fn has_incoming_edge_from_remaining(
503 candidate: TransformPassKind,
504 remaining: &[TransformPassKind],
505 registry_entries: &[TransformPassRegistryEntryV0],
506) -> bool {
507 descriptor_for_pass(candidate, registry_entries).is_some_and(|descriptor| {
508 descriptor
509 .depends_on
510 .iter()
511 .any(|dependency| remaining.iter().any(|other| other.id() == *dependency))
512 })
513}
514
515fn edge_applies(edge: &TransformDagEdgeV0, ordered_pass_ids: &[&'static str]) -> bool {
516 ordered_pass_ids.contains(&edge.from) && ordered_pass_ids.contains(&edge.to)
517}
518
519fn edge_is_satisfied(edge: &TransformDagEdgeV0, ordered_pass_ids: &[&'static str]) -> bool {
520 let from = position_of_pass_id(edge.from, ordered_pass_ids);
521 let to = position_of_pass_id(edge.to, ordered_pass_ids);
522 match (from, to) {
523 (Some(from), Some(to)) => from < to,
524 _ => false,
525 }
526}
527
528fn dag_path_exists(from: &'static str, to: &'static str, dag_edges: &[TransformDagEdgeV0]) -> bool {
529 let mut stack = vec![from];
530 let mut visited = Vec::new();
531 while let Some(current) = stack.pop() {
532 if current == to {
533 return true;
534 }
535 if visited.contains(¤t) {
536 continue;
537 }
538 visited.push(current);
539 for edge in dag_edges.iter().filter(|edge| edge.from == current) {
540 stack.push(edge.to);
541 }
542 }
543 false
544}
545
546fn position_of_pass_id(pass_id: &'static str, ordered_pass_ids: &[&'static str]) -> Option<usize> {
547 ordered_pass_ids
548 .iter()
549 .position(|ordered_pass_id| *ordered_pass_id == pass_id)
550}
551
552fn contract_for_pass(
553 pass: TransformPassKind,
554 contracts: &[TransformPassContractV0],
555) -> Option<&TransformPassContractV0> {
556 contracts.iter().find(|contract| contract.kind == pass)
557}
558
559fn descriptor_for_pass(
560 pass: TransformPassKind,
561 registry_entries: &[TransformPassRegistryEntryV0],
562) -> Option<&TransformPassDescriptorV0> {
563 registry_entries
564 .iter()
565 .find(|entry| entry.descriptor.kind == pass)
566 .map(|entry| &entry.descriptor)
567}
568
569pub(crate) fn transform_pass_kind_from_id(pass_id: &str) -> Option<TransformPassKind> {
570 all_transform_pass_kinds()
571 .into_iter()
572 .find(|kind| kind.id() == pass_id)
573}