1use std::collections::BTreeSet;
8
9use omena_abstract_value::FactPrecision;
10use omena_cascade::StaticSupportsAssumptionV0;
11use omena_cascade_proof::{
12 CanonicalRewriteAssumptionsV0, DischargeLedgerLookupStatusV0,
13 REWRITE_CERTIFICATE_SCHEMA_VERSION_V0, REWRITE_RULE_CATALOG_SCHEMA_VERSION_V0,
14 RewriteCertificateEnvelopeV0, RewriteCertificateV0, RewriteOperatorV0, RewritePatternV0,
15 RewriteRuleCatalogV0, RewriteRuleV0, RewriteSideConditionKindV0, RewriteSubstitutionEntryV0,
16 RewriteTermV0, SideConditionCertV0, TokenOwnershipCertEntryV0,
17 TokenOwnershipSeparabilityCertV0, check_rewrite_certificate_v0,
18};
19use omena_evidence_graph::GuaranteeFamilyV0;
20use omena_parser::{
21 ClosedWorldBundleV0, ClosedWorldComposesScanStateV0, ClosedWorldInterfaceHashAvailabilityV0,
22 ClosedWorldModuleReachabilityEvidenceV0, ModuleInstanceKeyV0, ModuleQualifiedSymbolSetV0,
23 StyleDialect,
24};
25use omena_transform_cst::{
26 IrNodeKindV0, StableTransformIrNodeV0, TransformIrV0, TransformPassClassV0, TransformPassKind,
27 build_stable_transform_ir_from_source, lower_transform_ir_from_source,
28 strict_verification_build_profile, transform_pass_requires_closed_world_bundle,
29};
30
31use super::{
32 cascade_proof::{
33 collect_cascade_proof_obligations_for_ir_pass_input,
34 collect_cascade_proof_obligations_for_pass_input, summarize_cascade_proof_obligations,
35 },
36 outcome::{mutation_outcome, no_change_outcome, planned_only_outcome},
37 planner::{
38 default_transform_pass_registry, plan_transform_passes, transform_pass_kind_from_id,
39 },
40 provenance::{derive_transform_mutation_spans, provenance_derivation_forest_from_outcomes},
41 semantic_preservation::{
42 SemanticObservationProjectionV0, SemanticObservationScopeV0,
43 compare_semantic_observation_for_pass_with_scopes, semantic_preservation_applies,
44 },
45 winner_equality::{
46 TransformWinnerEqualityContextV0, TransformWinnerEqualityEvaluationV0,
47 driven_transform_axes, evaluate_transform_winner_equality, strict_required_winner_axes,
48 },
49};
50use crate::helpers::ir_transaction::{
51 reset_structural_ir_transaction_mutation_span_batches,
52 reset_structural_ir_transaction_telemetry, structural_ir_transaction_telemetry_snapshot,
53 take_structural_ir_transaction_mutation_span_batches,
54};
55use crate::model::{
56 CssModuleTokenOwnershipCensusV0, RollbackReceiptV0, RollbackScopeV0, TransformBlockedReasonV0,
57 TransformCascadeProofObligationV0, TransformClosedWorldAdmissionSummaryV0,
58 TransformCssModuleComposesResolutionV0, TransformDecision, TransformDesignTokenRouteV0,
59 TransformDischargeEvidenceV0, TransformDischargeLedgerTelemetryV0,
60 TransformEvaluationProfileV0, TransformExecutionContextV0, TransformExecutionPolicyV0,
61 TransformExecutionSummaryV0, TransformImportInlineV0, TransformModuleEvaluationNativeEditV0,
62 TransformModuleEvaluationV0, TransformModuleQualifiedExecutionErrorV0,
63 TransformModuleQualifiedShakeSummaryV0, TransformNoChangeReasonV0, TransformPassDispatchKindV0,
64 TransformPassExecutionOutcomeV0, TransformPassRegistryEntryV0, TransformPassRuntimeStatus,
65 TransformPreconditionV0, TransformProvenanceMutationSpanV0, TransformRejectionReasonV0,
66 TransformSemanticGuaranteeTierV0, TransformSemanticPreservationTelemetryV0,
67 TransformSemanticRemovalV0, TransformStrictPolicyReasonV0, TransformStrictPolicySummaryV0,
68 TransformStructuralDecisionClassV0, TransformStructuralDecisionPolicyV0,
69 TransformVendorPrefixPolicyV0, TransformWinnerEqualityAxisV0,
70 TransformWinnerEqualityObligationV0, TransformWinnerEqualityObservationV0,
71 transform_structural_decision_policy,
72};
73use crate::registry::{
74 add_css_vendor_prefixes, combine_css_shorthands, compress_css_colors,
75 compress_css_is_where_selectors, compress_css_numbers, css_module_composes_resolutions_for_ir,
76 dedupe_exact_css_rules_in_ir, evaluate_dead_media_branch_rules_in_ir,
77 evaluate_native_css_static_values_in_ir, evaluate_static_container_rules_in_ir,
78 evaluate_static_media_rules_in_ir, evaluate_static_supports_rules_in_ir,
79 flatten_css_layers_in_ir, flatten_css_scopes_in_ir, inline_css_imports_in_ir,
80 lower_css_color_function, lower_css_color_mix, lower_css_light_dark,
81 lower_css_logical_to_physical, lower_css_oklab_oklch, lower_relative_color,
82 merge_adjacent_same_block_css_selectors_in_ir, merge_adjacent_same_selector_css_rules_in_ir,
83 normalize_css_string_quotes, normalize_css_units, normalize_css_whitespace, reduce_css_calc,
84 remove_empty_css_rules_in_ir, remove_stale_css_vendor_prefixes,
85 resolve_css_module_composes_in_ir, resolve_static_css_modules_values,
86 rewrite_css_module_class_names_in_ir, route_design_token_values_in_ir, strip_css_comments,
87 strip_css_url_quotes, substitute_static_css_custom_properties,
88 tree_shake_css_class_rules_in_ir, tree_shake_css_custom_properties_in_ir,
89 tree_shake_css_keyframes_in_ir, tree_shake_css_modules_values_in_ir, unwrap_css_nesting_in_ir,
90};
91
92type TransformTextLocalRunnerV0 =
93 for<'a> fn(TransformTextLocalPassInputV0<'a>) -> TransformTextLocalPassOutputV0<'a>;
94
95#[derive(Clone, Copy)]
96struct TransformTextLocalPassHandlerV0 {
97 kind: TransformPassKind,
98 window_scope: TransformTextLocalWindowScopeV0,
99 execution_mode: TransformTextLocalExecutionModeV0,
100 detail: &'static str,
101 run: TransformTextLocalRunnerV0,
102}
103
104#[derive(Clone, Copy, PartialEq, Eq)]
105enum TransformTextLocalWindowScopeV0 {
106 DocumentTokenStream,
107 Selector,
108 DeclarationBlock,
109 DeclarationValue,
110}
111
112#[derive(Clone, Copy, PartialEq, Eq)]
113enum TransformTextLocalExecutionModeV0 {
114 FullDocument,
115 WindowBatch,
116}
117
118struct TransformPassDispatchResultV0 {
119 next_textual_css: Option<String>,
120 document_ir_updated: bool,
121 decision: TransformDecisionDraftV0,
122 css_module_evaluation: Option<TransformModuleEvaluationV0>,
123 css_import_inlines: Vec<TransformImportInlineV0>,
124 css_module_composes_exports: Vec<TransformCssModuleComposesResolutionV0>,
125 design_token_routes: Vec<TransformDesignTokenRouteV0>,
126 semantic_removals: Vec<TransformSemanticRemovalV0>,
127 provenance_mutation_spans: Option<Vec<TransformProvenanceMutationSpanV0>>,
128}
129
130enum TransformDecisionDraftV0 {
131 Applied {
132 outcome: TransformPassExecutionOutcomeV0,
133 },
134 NoChange {
135 reason: TransformNoChangeReasonV0,
136 outcome: TransformPassExecutionOutcomeV0,
137 },
138 Blocked {
139 reason: TransformBlockedReasonV0,
140 outcome: TransformPassExecutionOutcomeV0,
141 },
142 Rejected {
143 reason: TransformRejectionReasonV0,
144 outcome: TransformPassExecutionOutcomeV0,
145 },
146}
147
148#[derive(Clone, Copy)]
149enum TransformSemanticTrustRecordingV0 {
150 Record,
151 OmitForMeasurement,
152}
153
154impl TransformSemanticTrustRecordingV0 {
155 fn decision_tier(
156 self,
157 pass: Option<TransformPassKind>,
158 winner_tier: Option<TransformSemanticGuaranteeTierV0>,
159 ) -> Option<TransformSemanticGuaranteeTierV0> {
160 match self {
161 Self::Record => winner_tier.or_else(|| {
162 pass.filter(|pass| semantic_preservation_applies(*pass))
163 .map(|_| TransformSemanticGuaranteeTierV0::L0Observed)
164 }),
165 Self::OmitForMeasurement => None,
166 }
167 }
168
169 fn records(self) -> bool {
170 matches!(self, Self::Record)
171 }
172}
173
174#[derive(Clone, Copy)]
175struct TransformExecutionRuntimePolicyV0<'a> {
176 verification: &'a TransformExecutionPolicyV0,
177 semantic_trust_recording: TransformSemanticTrustRecordingV0,
178 module_qualified_symbols: Option<&'a ModuleQualifiedSymbolSetV0>,
179 retained_class_names: &'a [String],
180 token_ownership_census: Option<&'a CssModuleTokenOwnershipCensusV0>,
181 token_ownership_module_instance: Option<&'a ModuleInstanceKeyV0>,
182}
183
184impl TransformDecisionDraftV0 {
185 fn applied_outcome(&self) -> Option<&TransformPassExecutionOutcomeV0> {
186 match self {
187 Self::Applied { outcome } => Some(outcome),
188 Self::NoChange { .. } | Self::Blocked { .. } | Self::Rejected { .. } => None,
189 }
190 }
191
192 #[cfg(test)]
193 fn compatibility_outcome(&self) -> &TransformPassExecutionOutcomeV0 {
194 match self {
195 Self::Applied { outcome }
196 | Self::NoChange { outcome, .. }
197 | Self::Blocked { outcome, .. }
198 | Self::Rejected { outcome, .. } => outcome,
199 }
200 }
201
202 fn finalize(
203 self,
204 input_content_signature: String,
205 preserved_output_signature: String,
206 discharge_evidence: Vec<TransformDischargeEvidenceV0>,
207 semantic_guarantee_tier: Option<TransformSemanticGuaranteeTierV0>,
208 ) -> TransformDecision {
209 match self {
210 Self::Applied { outcome } => TransformDecision::Applied {
211 rollback_receipt: RollbackReceiptV0 {
212 pass_id: outcome.pass_id.to_string(),
213 attempted_mutation_count: Some(outcome.mutation_count),
214 input_content_signature,
215 output_preserved_content_signature: None,
216 restorable: RollbackScopeV0::CommittedIrrecoverable,
217 },
218 semantic_guarantee_tier,
219 discharge_evidence,
220 outcome,
221 },
222 Self::NoChange { reason, outcome } => TransformDecision::NoChange { reason, outcome },
223 Self::Blocked { reason, outcome } => TransformDecision::Blocked { reason, outcome },
224 Self::Rejected { reason, outcome } => {
225 assert_eq!(input_content_signature, preserved_output_signature);
226 TransformDecision::Rejected {
227 rollback_receipt: RollbackReceiptV0 {
228 pass_id: outcome.pass_id.to_string(),
229 attempted_mutation_count: None,
230 input_content_signature,
231 output_preserved_content_signature: Some(preserved_output_signature),
232 restorable: RollbackScopeV0::RejectPreservedInput,
233 },
234 reason,
235 outcome,
236 }
237 }
238 }
239 }
240}
241
242#[derive(Clone, Copy)]
243enum TransformRuntimePassImplementationV0 {
244 TextLocal(TransformTextLocalPassHandlerV0),
245 Structural(TransformStructuralPassHandlerV0),
246 ModuleEvaluation(TransformPassKind),
247 Emission(TransformPassKind),
248}
249
250struct TransformRuntimePassEntryV0<'a> {
251 registry_entry: &'a TransformPassRegistryEntryV0,
252 implementation: TransformRuntimePassImplementationV0,
253}
254
255fn runtime_pass_entry_for_kind<'a>(
256 kind: TransformPassKind,
257 registry_entries: &'a [TransformPassRegistryEntryV0],
258) -> Option<TransformRuntimePassEntryV0<'a>> {
259 let registry_entry = registry_entries
260 .iter()
261 .find(|entry| entry.contract.kind == kind)?;
262 let implementation = runtime_pass_implementation_for_entry(registry_entry)?;
263 Some(TransformRuntimePassEntryV0 {
264 registry_entry,
265 implementation,
266 })
267}
268
269fn runtime_pass_implementation_for_entry(
270 entry: &TransformPassRegistryEntryV0,
271) -> Option<TransformRuntimePassImplementationV0> {
272 match entry.dispatch_kind {
273 TransformPassDispatchKindV0::TextLocalSliceRewrite => text_local_pass_handlers()
274 .iter()
275 .find(|handler| handler.kind == entry.contract.kind)
276 .copied()
277 .map(TransformRuntimePassImplementationV0::TextLocal),
278 TransformPassDispatchKindV0::StructuralIrTransaction => structural_pass_handlers()
279 .iter()
280 .find(|handler| handler.kind == entry.contract.kind)
281 .copied()
282 .map(TransformRuntimePassImplementationV0::Structural),
283 TransformPassDispatchKindV0::ModuleEvaluationHandler => Some(
284 TransformRuntimePassImplementationV0::ModuleEvaluation(entry.contract.kind),
285 ),
286 TransformPassDispatchKindV0::EmissionBoundary => Some(
287 TransformRuntimePassImplementationV0::Emission(entry.contract.kind),
288 ),
289 }
290}
291
292#[derive(Default)]
293struct TransformTextualBridgeSnapshotV0 {
294 css: Option<String>,
295}
296
297impl TransformTextualBridgeSnapshotV0 {
298 fn materialize_current_css<'a>(
299 &'a mut self,
300 document: &TransformExecutionDocumentV0,
301 ) -> &'a str {
302 self.css
303 .get_or_insert_with(|| document.current_css().to_string())
304 .as_str()
305 }
306}
307
308impl TransformPassDispatchResultV0 {
309 fn semantic_mutation_spans(&self) -> Vec<TransformProvenanceMutationSpanV0> {
310 let Some(outcome) = self.decision.applied_outcome() else {
311 return Vec::new();
312 };
313 if outcome.mutation_count == 0 {
314 return Vec::new();
315 }
316 self.provenance_mutation_spans.clone().unwrap_or_else(|| {
317 vec![TransformProvenanceMutationSpanV0 {
318 source_span_start: 0,
319 source_span_end: outcome.input_byte_len,
320 generated_span_start: 0,
321 generated_span_end: outcome.output_byte_len,
322 node_key: None,
323 }]
324 })
325 }
326
327 fn from_decision(next_textual_css: Option<String>, decision: TransformDecisionDraftV0) -> Self {
328 Self {
329 next_textual_css,
330 document_ir_updated: false,
331 decision,
332 css_module_evaluation: None,
333 css_import_inlines: Vec::new(),
334 css_module_composes_exports: Vec::new(),
335 design_token_routes: Vec::new(),
336 semantic_removals: Vec::new(),
337 provenance_mutation_spans: None,
338 }
339 }
340
341 fn textual_mutation(
342 pass_id: &'static str,
343 input_byte_len: usize,
344 next_css: String,
345 mutation_count: usize,
346 detail: &'static str,
347 ) -> Self {
348 let outcome = mutation_outcome(
349 pass_id,
350 input_byte_len,
351 next_css.len(),
352 mutation_count,
353 detail,
354 );
355 Self::from_mutation_outcome(Some(next_css), outcome)
356 }
357
358 fn ir_mutation(
359 pass_id: &'static str,
360 input_byte_len: usize,
361 output_byte_len: usize,
362 mutation_count: usize,
363 detail: &'static str,
364 ) -> Self {
365 let outcome = mutation_outcome(
366 pass_id,
367 input_byte_len,
368 output_byte_len,
369 mutation_count,
370 detail,
371 );
372 let mut result = Self::from_mutation_outcome(None, outcome);
373 result.document_ir_updated = true;
374 result
375 }
376
377 fn from_mutation_outcome(
378 next_textual_css: Option<String>,
379 outcome: TransformPassExecutionOutcomeV0,
380 ) -> Self {
381 let decision = match outcome.status {
382 TransformPassRuntimeStatus::Applied => TransformDecisionDraftV0::Applied { outcome },
383 TransformPassRuntimeStatus::NoChange => TransformDecisionDraftV0::NoChange {
384 reason: TransformNoChangeReasonV0::NoMutation,
385 outcome,
386 },
387 TransformPassRuntimeStatus::PlannedOnly => {
388 unreachable!("mutation outcomes cannot be planned-only")
389 }
390 };
391 Self::from_decision(next_textual_css, decision)
392 }
393
394 fn no_change(
395 pass_id: &'static str,
396 input_byte_len: usize,
397 reason: TransformNoChangeReasonV0,
398 detail: &'static str,
399 ) -> Self {
400 let outcome = no_change_outcome(pass_id, input_byte_len, input_byte_len, detail);
401 Self::from_decision(None, TransformDecisionDraftV0::NoChange { reason, outcome })
402 }
403
404 fn profile_only(
405 pass_id: &'static str,
406 input_byte_len: usize,
407 profile: TransformEvaluationProfileV0,
408 detail: &'static str,
409 ) -> Self {
410 let outcome = planned_only_outcome(pass_id, input_byte_len, input_byte_len, detail);
411 Self::from_decision(
412 None,
413 TransformDecisionDraftV0::NoChange {
414 reason: TransformNoChangeReasonV0::ProfileNotApplicable { profile },
415 outcome,
416 },
417 )
418 }
419
420 fn blocked(
421 pass_id: &'static str,
422 input_byte_len: usize,
423 reason: TransformBlockedReasonV0,
424 detail: &'static str,
425 ) -> Self {
426 let outcome = planned_only_outcome(pass_id, input_byte_len, input_byte_len, detail);
427 Self::from_decision(None, TransformDecisionDraftV0::Blocked { reason, outcome })
428 }
429
430 fn rejected(
431 pass_id: &'static str,
432 input_byte_len: usize,
433 reason: TransformRejectionReasonV0,
434 detail: &'static str,
435 ) -> Self {
436 let outcome = planned_only_outcome(pass_id, input_byte_len, input_byte_len, detail);
437 Self::from_decision(None, TransformDecisionDraftV0::Rejected { reason, outcome })
438 }
439}
440
441fn text_local_pass_handlers() -> &'static [TransformTextLocalPassHandlerV0] {
442 &TEXT_LOCAL_PASS_HANDLERS
443}
444
445fn text_local_execution_mode_for_kind(
446 kind: TransformPassKind,
447) -> Option<TransformTextLocalExecutionModeV0> {
448 text_local_pass_handlers()
449 .iter()
450 .find(|handler| handler.kind == kind)
451 .map(|handler| handler.execution_mode)
452}
453
454#[derive(Clone, Copy)]
455struct TransformTextLocalIrWindowV0<'a> {
456 source: &'a str,
457 source_span_start: usize,
458 source_span_end: usize,
459}
460
461impl<'a> TransformTextLocalIrWindowV0<'a> {
462 fn full_document(ir: &'a TransformIrV0) -> Vec<Self> {
463 vec![Self {
464 source: ir.source_text(),
465 source_span_start: 0,
466 source_span_end: ir.source_text().len(),
467 }]
468 }
469
470 fn for_scope(ir: &'a TransformIrV0, scope: TransformTextLocalWindowScopeV0) -> Vec<Self> {
471 match scope {
472 TransformTextLocalWindowScopeV0::DocumentTokenStream
473 | TransformTextLocalWindowScopeV0::Selector => Self::full_document(ir),
474 TransformTextLocalWindowScopeV0::DeclarationBlock
475 | TransformTextLocalWindowScopeV0::DeclarationValue => {
476 Self::root_rule_windows(ir).unwrap_or_else(|| Self::full_document(ir))
477 }
478 }
479 }
480
481 fn root_rule_windows(ir: &'a TransformIrV0) -> Option<Vec<Self>> {
482 let mut windows = ir
483 .root_nodes
484 .iter()
485 .filter_map(|node_id| {
486 let node = ir.nodes.get(node_id.index())?;
487 matches!(node.kind, IrNodeKindV0::StyleRule | IrNodeKindV0::AtRule).then(|| Self {
488 source: ir.source_text(),
489 source_span_start: node.source_span_start,
490 source_span_end: node.source_span_end,
491 })
492 })
493 .collect::<Vec<_>>();
494 windows.sort_by_key(|window| (window.source_span_start, window.source_span_end));
495 (!windows.is_empty() && windows_are_non_overlapping(windows.as_slice())).then_some(windows)
496 }
497
498 fn source_text(self) -> &'a str {
499 self.source
500 .get(self.source_span_start..self.source_span_end)
501 .unwrap_or_default()
502 }
503}
504
505fn windows_are_non_overlapping(windows: &[TransformTextLocalIrWindowV0<'_>]) -> bool {
506 windows
507 .windows(2)
508 .all(|pair| pair[0].source_span_end <= pair[1].source_span_start)
509}
510
511struct TransformTextLocalPassInputV0<'a> {
512 source: &'a str,
513 source_windows: Vec<TransformTextLocalIrWindowV0<'a>>,
514 execution_mode: TransformTextLocalExecutionModeV0,
515 dialect: StyleDialect,
516 context: &'a TransformExecutionContextV0,
517}
518
519impl<'a> TransformTextLocalPassInputV0<'a> {
520 fn from_ir(
521 ir: &'a TransformIrV0,
522 scope: TransformTextLocalWindowScopeV0,
523 execution_mode: TransformTextLocalExecutionModeV0,
524 dialect: StyleDialect,
525 context: &'a TransformExecutionContextV0,
526 ) -> Self {
527 Self {
528 source: ir.source_text(),
529 source_windows: TransformTextLocalIrWindowV0::for_scope(ir, scope),
530 execution_mode,
531 dialect,
532 context,
533 }
534 }
535
536 fn rewrite_text_local(
537 self,
538 rewrite: impl FnMut(&str, StyleDialect, &TransformExecutionContextV0) -> (String, usize),
539 ) -> TransformTextLocalPassOutputV0<'a> {
540 match self.execution_mode {
541 TransformTextLocalExecutionModeV0::FullDocument => self.rewrite_full_document(rewrite),
542 TransformTextLocalExecutionModeV0::WindowBatch => self.rewrite_windows(rewrite),
543 }
544 }
545
546 fn rewrite_windows(
547 self,
548 mut rewrite: impl FnMut(&str, StyleDialect, &TransformExecutionContextV0) -> (String, usize),
549 ) -> TransformTextLocalPassOutputV0<'a> {
550 let mut rewrites = Vec::new();
551 let mut mutation_count = 0usize;
552 for window in &self.source_windows {
553 let window_source = window.source_text();
554 let (rewritten_css, window_mutation_count) =
555 rewrite(window_source, self.dialect, self.context);
556 mutation_count += window_mutation_count;
557 if window_mutation_count > 0 || rewritten_css != window_source {
558 rewrites.push(TransformTextLocalWindowRewriteV0 {
559 source_span_start: window.source_span_start,
560 source_span_end: window.source_span_end,
561 rewritten_css,
562 });
563 }
564 }
565
566 TransformTextLocalPassOutputV0 {
567 input_byte_len: self.source.len(),
568 rewritten_css: apply_text_local_window_rewrites(self.source, rewrites.as_slice()),
569 provenance_mutation_spans: derive_text_local_window_mutation_spans(
570 self.source,
571 rewrites.as_slice(),
572 ),
573 mutation_count,
574 _lifetime: std::marker::PhantomData,
575 }
576 }
577
578 fn rewrite_full_document(
579 self,
580 mut rewrite: impl FnMut(&str, StyleDialect, &TransformExecutionContextV0) -> (String, usize),
581 ) -> TransformTextLocalPassOutputV0<'a> {
582 let (rewritten_css, mutation_count) = rewrite(self.source, self.dialect, self.context);
583 let provenance_mutation_spans =
584 derive_transform_mutation_spans(self.source, rewritten_css.as_str());
585 TransformTextLocalPassOutputV0 {
586 input_byte_len: self.source.len(),
587 rewritten_css,
588 provenance_mutation_spans,
589 mutation_count,
590 _lifetime: std::marker::PhantomData,
591 }
592 }
593}
594
595struct TransformTextLocalWindowRewriteV0 {
596 source_span_start: usize,
597 source_span_end: usize,
598 rewritten_css: String,
599}
600
601struct TransformTextLocalPassOutputV0<'a> {
602 input_byte_len: usize,
603 rewritten_css: String,
604 provenance_mutation_spans: Vec<TransformProvenanceMutationSpanV0>,
605 mutation_count: usize,
606 _lifetime: std::marker::PhantomData<&'a str>,
607}
608
609impl TransformTextLocalPassOutputV0<'_> {
610 fn input_byte_len(&self) -> usize {
611 self.input_byte_len
612 }
613
614 fn provenance_mutation_spans(&self) -> &[TransformProvenanceMutationSpanV0] {
615 self.provenance_mutation_spans.as_slice()
616 }
617
618 fn into_document_css(self) -> String {
619 self.rewritten_css
620 }
621}
622
623fn apply_text_local_window_rewrites(
624 source: &str,
625 rewrites: &[TransformTextLocalWindowRewriteV0],
626) -> String {
627 if rewrites.is_empty() {
628 return source.to_string();
629 }
630
631 let mut output = String::with_capacity(source.len());
632 let mut cursor = 0usize;
633 for rewrite in rewrites {
634 if rewrite.source_span_start < cursor {
635 continue;
636 }
637 if rewrite.source_span_start > cursor {
638 output.push_str(&source[cursor..rewrite.source_span_start]);
639 }
640 output.push_str(&rewrite.rewritten_css);
641 cursor = rewrite.source_span_end;
642 }
643 if cursor < source.len() {
644 output.push_str(&source[cursor..]);
645 }
646 output
647}
648
649fn derive_text_local_window_mutation_spans(
650 source: &str,
651 rewrites: &[TransformTextLocalWindowRewriteV0],
652) -> Vec<TransformProvenanceMutationSpanV0> {
653 let mut spans = Vec::new();
654 let mut generated_delta = 0isize;
655 for rewrite in rewrites {
656 let Some(source_window) = source.get(rewrite.source_span_start..rewrite.source_span_end)
657 else {
658 let rewritten_css = apply_text_local_window_rewrites(source, rewrites);
659 return derive_transform_mutation_spans(source, rewritten_css.as_str());
660 };
661 let Some(generated_window_start) =
662 add_signed_offset(rewrite.source_span_start, generated_delta)
663 else {
664 let rewritten_css = apply_text_local_window_rewrites(source, rewrites);
665 return derive_transform_mutation_spans(source, rewritten_css.as_str());
666 };
667 spans.extend(
668 derive_transform_mutation_spans(source_window, &rewrite.rewritten_css)
669 .into_iter()
670 .map(|span| TransformProvenanceMutationSpanV0 {
671 source_span_start: rewrite.source_span_start + span.source_span_start,
672 source_span_end: rewrite.source_span_start + span.source_span_end,
673 generated_span_start: generated_window_start + span.generated_span_start,
674 generated_span_end: generated_window_start + span.generated_span_end,
675 node_key: None,
676 }),
677 );
678 generated_delta += rewrite.rewritten_css.len() as isize
679 - rewrite
680 .source_span_end
681 .saturating_sub(rewrite.source_span_start) as isize;
682 }
683 spans
684}
685
686fn add_signed_offset(value: usize, offset: isize) -> Option<usize> {
687 if offset >= 0 {
688 value.checked_add(offset as usize)
689 } else {
690 value.checked_sub((-offset) as usize)
691 }
692}
693
694type TransformStructuralRunnerV0 =
695 for<'a> fn(TransformStructuralPassInputV0<'a>) -> TransformPassDispatchResultV0;
696
697#[derive(Clone, Copy)]
698struct TransformStructuralPassHandlerV0 {
699 kind: TransformPassKind,
700 run: TransformStructuralRunnerV0,
701}
702
703struct TransformStructuralPassInputV0<'a> {
704 pass_id: &'static str,
705 kind: TransformPassKind,
706 decision_policy: &'static TransformStructuralDecisionPolicyV0,
707 reachability_precision: FactPrecision,
708 current_ir: &'a mut TransformIrV0,
709 input_byte_len: usize,
710 dialect: StyleDialect,
711 context: &'a TransformExecutionContextV0,
712 closed_world_bundle: Option<&'a ClosedWorldBundleV0>,
713 module_qualified_symbols: Option<&'a ModuleQualifiedSymbolSetV0>,
714 module_reachable_class_names: Option<&'a [String]>,
715}
716
717impl TransformStructuralPassInputV0<'_> {
718 fn current_ir_mut(&mut self) -> &mut TransformIrV0 {
719 self.current_ir
720 }
721
722 fn closed_world_bundle(&self) -> Option<&ClosedWorldBundleV0> {
723 self.closed_world_bundle
724 }
725
726 fn precision_blocker(&self) -> Option<TransformBlockedReasonV0> {
727 let required = self.decision_policy.required_precision()?;
728 (!self.reachability_precision.satisfies(required)).then_some(
729 TransformBlockedReasonV0::PrecisionBelowFloor {
730 required,
731 observed: self.reachability_precision,
732 },
733 )
734 }
735
736 fn ir_mutation_result(
737 &self,
738 mutation_count: usize,
739 detail: &'static str,
740 ) -> TransformPassDispatchResultV0 {
741 TransformPassDispatchResultV0::ir_mutation(
742 self.pass_id,
743 self.input_byte_len,
744 self.current_ir.source_text().len(),
745 mutation_count,
746 detail,
747 )
748 }
749
750 fn ir_rejected(&self, detail: &'static str) -> TransformPassDispatchResultV0 {
751 TransformPassDispatchResultV0::rejected(
752 self.pass_id,
753 self.input_byte_len,
754 TransformRejectionReasonV0::IrTransaction { pass: self.kind },
755 detail,
756 )
757 }
758}
759
760fn structural_pass_handlers() -> &'static [TransformStructuralPassHandlerV0] {
761 &STRUCTURAL_PASS_HANDLERS
762}
763
764struct TransformExecutionDocumentV0 {
765 current_ir: TransformIrV0,
766 dialect: StyleDialect,
767}
768
769impl TransformExecutionDocumentV0 {
770 fn new(source: &str, dialect: StyleDialect) -> Self {
771 Self {
772 current_ir: lower_transform_ir_from_source(
773 source,
774 dialect,
775 "omena-transform-passes.execution.current",
776 ),
777 dialect,
778 }
779 }
780
781 fn current_ir_mut(&mut self) -> &mut TransformIrV0 {
782 &mut self.current_ir
783 }
784
785 fn current_css(&self) -> &str {
786 self.current_ir.source_text()
787 }
788
789 fn current_byte_len(&self) -> usize {
790 self.current_css().len()
791 }
792
793 fn replace_with_css(&mut self, css: String) {
794 self.current_ir = lower_transform_ir_from_source(
795 css.as_str(),
796 self.dialect,
797 "omena-transform-passes.execution.current",
798 );
799 }
800
801 fn output_css(&self) -> String {
802 self.current_css().to_string()
803 }
804}
805
806fn transform_content_signature(source: &str) -> String {
807 blake3::hash(source.as_bytes()).to_hex().to_string()
808}
809
810static TEXT_LOCAL_PASS_HANDLERS: [TransformTextLocalPassHandlerV0; 20] = [
811 TransformTextLocalPassHandlerV0 {
812 kind: TransformPassKind::WhitespaceStrip,
813 window_scope: TransformTextLocalWindowScopeV0::DocumentTokenStream,
814 execution_mode: TransformTextLocalExecutionModeV0::FullDocument,
815 detail: "normalized lexer trivia where adjacent token boundaries remain unambiguous",
816 run: run_whitespace_strip_text_local,
817 },
818 TransformTextLocalPassHandlerV0 {
819 kind: TransformPassKind::CommentStrip,
820 window_scope: TransformTextLocalWindowScopeV0::DocumentTokenStream,
821 execution_mode: TransformTextLocalExecutionModeV0::FullDocument,
822 detail: "removed CSS block comments outside string literals",
823 run: run_comment_strip_text_local,
824 },
825 TransformTextLocalPassHandlerV0 {
826 kind: TransformPassKind::NumberCompression,
827 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
828 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
829 detail: "compressed lexer numeric tokens without touching identifiers or strings",
830 run: run_number_compression_text_local,
831 },
832 TransformTextLocalPassHandlerV0 {
833 kind: TransformPassKind::UnitNormalization,
834 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
835 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
836 detail: "normalized zero length units and known CSS unit casing inside declaration contexts",
837 run: run_unit_normalization_text_local,
838 },
839 TransformTextLocalPassHandlerV0 {
840 kind: TransformPassKind::ColorCompression,
841 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
842 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
843 detail: "compressed static declaration color values and hex color tokens",
844 run: run_color_compression_text_local,
845 },
846 TransformTextLocalPassHandlerV0 {
847 kind: TransformPassKind::UrlQuoteStrip,
848 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
849 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
850 detail: "stripped quotes from safe url() string arguments",
851 run: run_url_quote_strip_text_local,
852 },
853 TransformTextLocalPassHandlerV0 {
854 kind: TransformPassKind::StringQuoteNormalize,
855 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
856 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
857 detail: "normalized safe CSS string tokens, declaration-scoped font family strings, and static font keyword aliases",
858 run: run_string_quote_normalize_text_local,
859 },
860 TransformTextLocalPassHandlerV0 {
861 kind: TransformPassKind::SelectorIsWhereCompression,
862 window_scope: TransformTextLocalWindowScopeV0::Selector,
863 execution_mode: TransformTextLocalExecutionModeV0::FullDocument,
864 detail: "compressed :is/:where selector functions and keyframe selector aliases only when matching semantics are preserved",
865 run: run_selector_is_where_compression_text_local,
866 },
867 TransformTextLocalPassHandlerV0 {
868 kind: TransformPassKind::ShorthandCombining,
869 window_scope: TransformTextLocalWindowScopeV0::DeclarationBlock,
870 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
871 detail: "combined safe shorthand declarations and adjacent longhands only with cascade-preserving proofs",
872 run: run_shorthand_combining_text_local,
873 },
874 TransformTextLocalPassHandlerV0 {
875 kind: TransformPassKind::VendorPrefixing,
876 window_scope: TransformTextLocalWindowScopeV0::DeclarationBlock,
877 execution_mode: TransformTextLocalExecutionModeV0::FullDocument,
878 detail: "inserted target-aware vendor-prefixed declaration synonyms when absent",
879 run: run_vendor_prefixing_text_local,
880 },
881 TransformTextLocalPassHandlerV0 {
882 kind: TransformPassKind::StalePrefixRemoval,
883 window_scope: TransformTextLocalWindowScopeV0::DeclarationBlock,
884 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
885 detail: "removed explicit stale prefixed declarations only when an exact unprefixed peer proves equivalence",
886 run: run_stale_prefix_removal_text_local,
887 },
888 TransformTextLocalPassHandlerV0 {
889 kind: TransformPassKind::LightDarkLowering,
890 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
891 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
892 detail: "lowered light-dark() color references into dark media branches",
893 run: run_light_dark_lowering_text_local,
894 },
895 TransformTextLocalPassHandlerV0 {
896 kind: TransformPassKind::ColorMixLowering,
897 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
898 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
899 detail: "lowered static srgb color-mix() references with static color operands",
900 run: run_color_mix_lowering_text_local,
901 },
902 TransformTextLocalPassHandlerV0 {
903 kind: TransformPassKind::OklchOklabLowering,
904 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
905 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
906 detail: "lowered in-gamut oklab()/oklch() color references to srgb",
907 run: run_oklch_oklab_lowering_text_local,
908 },
909 TransformTextLocalPassHandlerV0 {
910 kind: TransformPassKind::ColorFunctionLowering,
911 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
912 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
913 detail: "lowered static color(...) references with static channels",
914 run: run_color_function_lowering_text_local,
915 },
916 TransformTextLocalPassHandlerV0 {
917 kind: TransformPassKind::RelativeColorLowering,
918 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
919 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
920 detail: "lowered static rgb(from ...) relative-color references to absolute srgb",
921 run: run_relative_color_lowering_text_local,
922 },
923 TransformTextLocalPassHandlerV0 {
924 kind: TransformPassKind::LogicalToPhysical,
925 window_scope: TransformTextLocalWindowScopeV0::DeclarationBlock,
926 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
927 detail: "lowered logical properties only under static horizontal writing direction",
928 run: run_logical_to_physical_text_local,
929 },
930 TransformTextLocalPassHandlerV0 {
931 kind: TransformPassKind::ValueResolution,
932 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
933 execution_mode: TransformTextLocalExecutionModeV0::FullDocument,
934 detail: "resolved whole-value references from unique local literal CSS Modules @value declarations",
935 run: run_value_resolution_text_local,
936 },
937 TransformTextLocalPassHandlerV0 {
938 kind: TransformPassKind::StaticVarSubstitution,
939 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
940 execution_mode: TransformTextLocalExecutionModeV0::FullDocument,
941 detail: "resolved whole-value var() references from unique static :root custom properties",
942 run: run_static_var_substitution_text_local,
943 },
944 TransformTextLocalPassHandlerV0 {
945 kind: TransformPassKind::CalcReduction,
946 window_scope: TransformTextLocalWindowScopeV0::DeclarationValue,
947 execution_mode: TransformTextLocalExecutionModeV0::WindowBatch,
948 detail: "reduced whole-value CSS math functions with static same-unit arithmetic and identity operations",
949 run: run_calc_reduction_text_local,
950 },
951];
952
953static STRUCTURAL_PASS_HANDLERS: [TransformStructuralPassHandlerV0; 21] = [
954 TransformStructuralPassHandlerV0 {
955 kind: TransformPassKind::ImportInline,
956 run: run_import_inline_structural,
957 },
958 TransformStructuralPassHandlerV0 {
959 kind: TransformPassKind::ResolveCssModulesComposes,
960 run: run_resolve_css_modules_composes_structural,
961 },
962 TransformStructuralPassHandlerV0 {
963 kind: TransformPassKind::DesignTokenRouting,
964 run: run_design_token_routing_structural,
965 },
966 TransformStructuralPassHandlerV0 {
967 kind: TransformPassKind::HashCssModuleClassNames,
968 run: run_hash_css_module_class_names_structural,
969 },
970 TransformStructuralPassHandlerV0 {
971 kind: TransformPassKind::RuleDeduplication,
972 run: run_rule_deduplication_structural,
973 },
974 TransformStructuralPassHandlerV0 {
975 kind: TransformPassKind::RuleMerging,
976 run: run_rule_merging_structural,
977 },
978 TransformStructuralPassHandlerV0 {
979 kind: TransformPassKind::SelectorMerging,
980 run: run_selector_merging_structural,
981 },
982 TransformStructuralPassHandlerV0 {
983 kind: TransformPassKind::NestingUnwrap,
984 run: run_nesting_unwrap_structural,
985 },
986 TransformStructuralPassHandlerV0 {
987 kind: TransformPassKind::ScopeFlatten,
988 run: run_scope_flatten_structural,
989 },
990 TransformStructuralPassHandlerV0 {
991 kind: TransformPassKind::LayerFlatten,
992 run: run_layer_flatten_structural,
993 },
994 TransformStructuralPassHandlerV0 {
995 kind: TransformPassKind::SupportsStaticEval,
996 run: run_supports_static_eval_structural,
997 },
998 TransformStructuralPassHandlerV0 {
999 kind: TransformPassKind::MediaStaticEval,
1000 run: run_media_static_eval_structural,
1001 },
1002 TransformStructuralPassHandlerV0 {
1003 kind: TransformPassKind::ContainerStaticEval,
1004 run: run_container_static_eval_structural,
1005 },
1006 TransformStructuralPassHandlerV0 {
1007 kind: TransformPassKind::NativeCssStaticEval,
1008 run: run_native_css_static_eval_structural,
1009 },
1010 TransformStructuralPassHandlerV0 {
1011 kind: TransformPassKind::DeadMediaBranchRemoval,
1012 run: run_dead_media_branch_removal_structural,
1013 },
1014 TransformStructuralPassHandlerV0 {
1015 kind: TransformPassKind::DeadSupportsBranchRemoval,
1016 run: run_dead_supports_branch_removal_structural,
1017 },
1018 TransformStructuralPassHandlerV0 {
1019 kind: TransformPassKind::TreeShakeClass,
1020 run: run_tree_shake_class_structural,
1021 },
1022 TransformStructuralPassHandlerV0 {
1023 kind: TransformPassKind::TreeShakeKeyframes,
1024 run: run_tree_shake_keyframes_structural,
1025 },
1026 TransformStructuralPassHandlerV0 {
1027 kind: TransformPassKind::TreeShakeValue,
1028 run: run_tree_shake_value_structural,
1029 },
1030 TransformStructuralPassHandlerV0 {
1031 kind: TransformPassKind::TreeShakeCustomProperty,
1032 run: run_tree_shake_custom_property_structural,
1033 },
1034 TransformStructuralPassHandlerV0 {
1035 kind: TransformPassKind::EmptyRuleRemoval,
1036 run: run_empty_rule_removal_structural,
1037 },
1038];
1039
1040pub fn execute_transform_passes_on_source(
1041 source: &str,
1042 requested: &[TransformPassKind],
1043) -> TransformExecutionSummaryV0 {
1044 execute_transform_passes_on_source_with_dialect(source, StyleDialect::Css, requested)
1045}
1046
1047pub fn execute_transform_passes_on_source_with_dialect(
1048 source: &str,
1049 dialect: StyleDialect,
1050 requested: &[TransformPassKind],
1051) -> TransformExecutionSummaryV0 {
1052 let context = TransformExecutionContextV0::default();
1053 execute_transform_passes_on_source_with_dialect_and_context(
1054 source, dialect, requested, &context,
1055 )
1056}
1057
1058#[cfg(feature = "transform-catalog-trace")]
1059pub fn execute_transform_passes_on_source_with_transform_catalog_trace(
1060 source: &str,
1061 requested: &[TransformPassKind],
1062) -> (
1063 TransformExecutionSummaryV0,
1064 omena_lawvere::TransformCatalogModelTraceV0,
1065) {
1066 execute_transform_passes_on_source_with_transform_catalog_trace_and_dialect(
1067 source,
1068 StyleDialect::Css,
1069 requested,
1070 )
1071}
1072
1073#[cfg(feature = "transform-catalog-trace")]
1074pub fn execute_transform_passes_on_source_with_transform_catalog_trace_and_dialect(
1075 source: &str,
1076 dialect: StyleDialect,
1077 requested: &[TransformPassKind],
1078) -> (
1079 TransformExecutionSummaryV0,
1080 omena_lawvere::TransformCatalogModelTraceV0,
1081) {
1082 let summary = execute_transform_passes_on_source_with_dialect(source, dialect, requested);
1083 let trace = omena_lawvere::trace_transform_catalog_model_v0(
1084 requested,
1085 summary.ordered_pass_ids.clone(),
1086 );
1087 (summary, trace)
1088}
1089
1090#[cfg(feature = "transform-catalog-trace")]
1091pub fn evaluate_transform_catalog_reorderability_with_differential_corpus(
1092 left: TransformPassKind,
1093 right: TransformPassKind,
1094 fixtures: &[&str],
1095) -> (
1096 omena_lawvere::ReorderabilityCertificateV0,
1097 omena_lawvere::TransformCatalogDifferentialCommutativityWitnessV0,
1098) {
1099 let cases = fixtures
1100 .iter()
1101 .enumerate()
1102 .map(|(index, source)| {
1103 let left_first = execute_transform_passes_on_source(source, &[left]);
1104 let left_then_right =
1105 execute_transform_passes_on_source(&left_first.output_css, &[right]);
1106 let right_first = execute_transform_passes_on_source(source, &[right]);
1107 let right_then_left =
1108 execute_transform_passes_on_source(&right_first.output_css, &[left]);
1109 let left_then_right_mutation_count =
1110 left_first.mutation_count + left_then_right.mutation_count;
1111 let right_then_left_mutation_count =
1112 right_first.mutation_count + right_then_left.mutation_count;
1113
1114 omena_lawvere::TransformCatalogDifferentialCommutativityCaseV0 {
1115 label: format!("fixture-{index}"),
1116 input_css: (*source).to_string(),
1117 left_then_right_css: left_then_right.output_css.clone(),
1118 right_then_left_css: right_then_left.output_css.clone(),
1119 left_then_right_mutation_count,
1120 right_then_left_mutation_count,
1121 equal_output: left_then_right.output_css == right_then_left.output_css,
1122 }
1123 })
1124 .collect::<Vec<_>>();
1125 let witness =
1126 omena_lawvere::transform_catalog_differential_commutativity_witness_v0(left, right, cases);
1127 let certificate =
1128 omena_lawvere::transform_catalog_reorderability_certificate_from_differential_v0(
1129 left, right, &witness,
1130 );
1131 (certificate, witness)
1132}
1133
1134#[cfg(feature = "transform-catalog-trace")]
1135#[allow(deprecated)]
1136#[deprecated(
1137 since = "0.4.0",
1138 note = "use execute_transform_passes_on_source_with_transform_catalog_trace; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
1139)]
1140pub fn execute_transform_passes_on_source_with_lawvere_trace(
1141 source: &str,
1142 requested: &[TransformPassKind],
1143) -> (
1144 TransformExecutionSummaryV0,
1145 omena_lawvere::LawvereModelTraceV0,
1146) {
1147 execute_transform_passes_on_source_with_lawvere_trace_and_dialect(
1148 source,
1149 StyleDialect::Css,
1150 requested,
1151 )
1152}
1153
1154#[cfg(feature = "transform-catalog-trace")]
1155#[allow(deprecated)]
1156#[deprecated(
1157 since = "0.4.0",
1158 note = "use execute_transform_passes_on_source_with_transform_catalog_trace_and_dialect; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
1159)]
1160pub fn execute_transform_passes_on_source_with_lawvere_trace_and_dialect(
1161 source: &str,
1162 dialect: StyleDialect,
1163 requested: &[TransformPassKind],
1164) -> (
1165 TransformExecutionSummaryV0,
1166 omena_lawvere::LawvereModelTraceV0,
1167) {
1168 let summary = execute_transform_passes_on_source_with_dialect(source, dialect, requested);
1169 let trace = omena_lawvere::trace_lawvere_model_v0(requested, summary.ordered_pass_ids.clone());
1170 (summary, trace)
1171}
1172
1173#[cfg(feature = "transform-catalog-trace")]
1174#[allow(deprecated)]
1175#[deprecated(
1176 since = "0.4.0",
1177 note = "use evaluate_transform_catalog_reorderability_with_differential_corpus; removal is not before 1.0 and requires downstream migration plus zero audited non-compatibility uses"
1178)]
1179pub fn evaluate_lawvere_reorderability_with_differential_corpus(
1180 left: TransformPassKind,
1181 right: TransformPassKind,
1182 fixtures: &[&str],
1183) -> (
1184 omena_lawvere::ReorderabilityCertificateV0,
1185 omena_lawvere::LawvereDifferentialCommutativityWitnessV0,
1186) {
1187 let cases = fixtures
1188 .iter()
1189 .enumerate()
1190 .map(|(index, source)| {
1191 let left_first = execute_transform_passes_on_source(source, &[left]);
1192 let left_then_right =
1193 execute_transform_passes_on_source(&left_first.output_css, &[right]);
1194 let right_first = execute_transform_passes_on_source(source, &[right]);
1195 let right_then_left =
1196 execute_transform_passes_on_source(&right_first.output_css, &[left]);
1197 let left_then_right_mutation_count =
1198 left_first.mutation_count + left_then_right.mutation_count;
1199 let right_then_left_mutation_count =
1200 right_first.mutation_count + right_then_left.mutation_count;
1201
1202 omena_lawvere::LawvereDifferentialCommutativityCaseV0 {
1203 label: format!("fixture-{index}"),
1204 input_css: (*source).to_string(),
1205 left_then_right_css: left_then_right.output_css.clone(),
1206 right_then_left_css: right_then_left.output_css.clone(),
1207 left_then_right_mutation_count,
1208 right_then_left_mutation_count,
1209 equal_output: left_then_right.output_css == right_then_left.output_css,
1210 }
1211 })
1212 .collect::<Vec<_>>();
1213 let witness = omena_lawvere::lawvere_differential_commutativity_witness_v0(left, right, cases);
1214 let certificate =
1215 omena_lawvere::reorderability_certificate_from_differential_v0(left, right, &witness);
1216 (certificate, witness)
1217}
1218
1219pub fn execute_transform_passes_on_source_with_dialect_and_context(
1220 source: &str,
1221 dialect: StyleDialect,
1222 requested: &[TransformPassKind],
1223 context: &TransformExecutionContextV0,
1224) -> TransformExecutionSummaryV0 {
1225 execute_transform_passes_on_source_with_dialect_context_and_policy(
1226 source,
1227 dialect,
1228 requested,
1229 context,
1230 &TransformExecutionPolicyV0::default(),
1231 )
1232}
1233
1234pub fn execute_transform_passes_on_source_with_dialect_context_and_policy(
1235 source: &str,
1236 dialect: StyleDialect,
1237 requested: &[TransformPassKind],
1238 context: &TransformExecutionContextV0,
1239 execution_policy: &TransformExecutionPolicyV0,
1240) -> TransformExecutionSummaryV0 {
1241 super::lex_cache::with_transform_lex_cache(|| {
1242 execute_transform_passes_on_source_with_active_lex_cache(
1243 source,
1244 dialect,
1245 requested,
1246 context,
1247 None,
1248 None,
1249 TransformExecutionRuntimePolicyV0 {
1250 verification: execution_policy,
1251 semantic_trust_recording: TransformSemanticTrustRecordingV0::Record,
1252 module_qualified_symbols: None,
1253 retained_class_names: &[],
1254 token_ownership_census: None,
1255 token_ownership_module_instance: None,
1256 },
1257 )
1258 })
1259}
1260
1261pub fn execute_transform_passes_on_source_with_dialect_context_and_closed_world_bundle(
1262 source: &str,
1263 dialect: StyleDialect,
1264 requested: &[TransformPassKind],
1265 context: &TransformExecutionContextV0,
1266 closed_world_bundle: &ClosedWorldBundleV0,
1267) -> TransformExecutionSummaryV0 {
1268 execute_transform_passes_on_source_with_dialect_context_closed_world_bundle_and_precision(
1269 source,
1270 dialect,
1271 requested,
1272 context,
1273 closed_world_bundle,
1274 classify_transform_reachability_precision(context, true, None),
1275 )
1276}
1277
1278pub fn execute_transform_passes_on_source_with_dialect_context_closed_world_bundle_and_precision(
1279 source: &str,
1280 dialect: StyleDialect,
1281 requested: &[TransformPassKind],
1282 context: &TransformExecutionContextV0,
1283 closed_world_bundle: &ClosedWorldBundleV0,
1284 reachability_precision: FactPrecision,
1285) -> TransformExecutionSummaryV0 {
1286 execute_transform_passes_on_source_with_dialect_context_closed_world_bundle_precision_and_policy(
1287 source,
1288 dialect,
1289 requested,
1290 context,
1291 closed_world_bundle,
1292 reachability_precision,
1293 &TransformExecutionPolicyV0::default(),
1294 )
1295}
1296
1297pub fn execute_transform_passes_on_source_with_dialect_context_closed_world_bundle_precision_and_policy(
1298 source: &str,
1299 dialect: StyleDialect,
1300 requested: &[TransformPassKind],
1301 context: &TransformExecutionContextV0,
1302 closed_world_bundle: &ClosedWorldBundleV0,
1303 reachability_precision: FactPrecision,
1304 execution_policy: &TransformExecutionPolicyV0,
1305) -> TransformExecutionSummaryV0 {
1306 super::lex_cache::with_transform_lex_cache(|| {
1307 execute_transform_passes_on_source_with_active_lex_cache(
1308 source,
1309 dialect,
1310 requested,
1311 context,
1312 Some(closed_world_bundle),
1313 Some(reachability_precision),
1314 TransformExecutionRuntimePolicyV0 {
1315 verification: execution_policy,
1316 semantic_trust_recording: TransformSemanticTrustRecordingV0::Record,
1317 module_qualified_symbols: None,
1318 retained_class_names: &[],
1319 token_ownership_census: None,
1320 token_ownership_module_instance: None,
1321 },
1322 )
1323 })
1324}
1325
1326pub fn execute_transform_passes_on_module_with_dialect_context_and_closed_world_bundle(
1327 source: &str,
1328 dialect: StyleDialect,
1329 requested: &[TransformPassKind],
1330 context: &TransformExecutionContextV0,
1331 closed_world_bundle: &ClosedWorldBundleV0,
1332 module_instance: &ModuleInstanceKeyV0,
1333) -> Result<TransformExecutionSummaryV0, TransformModuleQualifiedExecutionErrorV0> {
1334 execute_transform_passes_on_module_with_dialect_context_policy_and_closed_world_bundle(
1335 source,
1336 dialect,
1337 requested,
1338 context,
1339 closed_world_bundle,
1340 module_instance,
1341 classify_transform_reachability_precision(context, true, None),
1342 &TransformExecutionPolicyV0::default(),
1343 )
1344}
1345
1346#[allow(clippy::too_many_arguments)]
1348pub fn execute_transform_passes_on_module_with_dialect_context_policy_and_closed_world_bundle(
1349 source: &str,
1350 dialect: StyleDialect,
1351 requested: &[TransformPassKind],
1352 context: &TransformExecutionContextV0,
1353 closed_world_bundle: &ClosedWorldBundleV0,
1354 module_instance: &ModuleInstanceKeyV0,
1355 reachability_precision: FactPrecision,
1356 execution_policy: &TransformExecutionPolicyV0,
1357) -> Result<TransformExecutionSummaryV0, TransformModuleQualifiedExecutionErrorV0> {
1358 execute_transform_passes_on_module_with_dialect_context_policy_and_closed_world_bundle_and_retained_class_names(
1359 source,
1360 dialect,
1361 requested,
1362 context,
1363 closed_world_bundle,
1364 module_instance,
1365 reachability_precision,
1366 execution_policy,
1367 &[],
1368 )
1369}
1370
1371#[allow(clippy::too_many_arguments)]
1373pub fn execute_transform_passes_on_module_with_dialect_context_policy_and_closed_world_bundle_and_retained_class_names(
1374 source: &str,
1375 dialect: StyleDialect,
1376 requested: &[TransformPassKind],
1377 context: &TransformExecutionContextV0,
1378 closed_world_bundle: &ClosedWorldBundleV0,
1379 module_instance: &ModuleInstanceKeyV0,
1380 reachability_precision: FactPrecision,
1381 execution_policy: &TransformExecutionPolicyV0,
1382 retained_class_names: &[String],
1383) -> Result<TransformExecutionSummaryV0, TransformModuleQualifiedExecutionErrorV0> {
1384 execute_transform_passes_on_module_with_optional_token_ownership_census(
1385 source,
1386 dialect,
1387 requested,
1388 context,
1389 closed_world_bundle,
1390 module_instance,
1391 reachability_precision,
1392 execution_policy,
1393 retained_class_names,
1394 None,
1395 None,
1396 )
1397}
1398
1399impl CssModuleTokenOwnershipCensusV0 {
1400 #[allow(clippy::too_many_arguments)]
1402 pub fn execute_module_transform_passes_with_ownership_admission(
1403 &self,
1404 source: &str,
1405 dialect: StyleDialect,
1406 requested: &[TransformPassKind],
1407 context: &TransformExecutionContextV0,
1408 closed_world_bundle: &ClosedWorldBundleV0,
1409 module_instance: &ModuleInstanceKeyV0,
1410 reachability_precision: FactPrecision,
1411 execution_policy: &TransformExecutionPolicyV0,
1412 retained_class_names: &[String],
1413 ) -> Result<TransformExecutionSummaryV0, TransformModuleQualifiedExecutionErrorV0> {
1414 self.execute_module_transform_passes_with_ownership_admission_for_identity(
1415 source,
1416 dialect,
1417 requested,
1418 context,
1419 closed_world_bundle,
1420 module_instance,
1421 module_instance,
1422 reachability_precision,
1423 execution_policy,
1424 retained_class_names,
1425 )
1426 }
1427
1428 #[allow(clippy::too_many_arguments)]
1434 pub fn execute_module_transform_passes_with_ownership_admission_for_identity(
1435 &self,
1436 source: &str,
1437 dialect: StyleDialect,
1438 requested: &[TransformPassKind],
1439 context: &TransformExecutionContextV0,
1440 closed_world_bundle: &ClosedWorldBundleV0,
1441 module_instance: &ModuleInstanceKeyV0,
1442 ownership_module_instance: &ModuleInstanceKeyV0,
1443 reachability_precision: FactPrecision,
1444 execution_policy: &TransformExecutionPolicyV0,
1445 retained_class_names: &[String],
1446 ) -> Result<TransformExecutionSummaryV0, TransformModuleQualifiedExecutionErrorV0> {
1447 execute_transform_passes_on_module_with_optional_token_ownership_census(
1448 source,
1449 dialect,
1450 requested,
1451 context,
1452 closed_world_bundle,
1453 module_instance,
1454 reachability_precision,
1455 execution_policy,
1456 retained_class_names,
1457 Some(self),
1458 Some(ownership_module_instance),
1459 )
1460 }
1461}
1462
1463#[allow(clippy::too_many_arguments)]
1464fn execute_transform_passes_on_module_with_optional_token_ownership_census(
1465 source: &str,
1466 dialect: StyleDialect,
1467 requested: &[TransformPassKind],
1468 context: &TransformExecutionContextV0,
1469 closed_world_bundle: &ClosedWorldBundleV0,
1470 module_instance: &ModuleInstanceKeyV0,
1471 reachability_precision: FactPrecision,
1472 execution_policy: &TransformExecutionPolicyV0,
1473 retained_class_names: &[String],
1474 token_ownership_census: Option<&CssModuleTokenOwnershipCensusV0>,
1475 token_ownership_module_instance: Option<&ModuleInstanceKeyV0>,
1476) -> Result<TransformExecutionSummaryV0, TransformModuleQualifiedExecutionErrorV0> {
1477 let Some(module_qualified_symbols) = closed_world_bundle
1478 .reachability()
1479 .symbols_for_module(module_instance)
1480 else {
1481 return Err(
1482 TransformModuleQualifiedExecutionErrorV0::UnknownModuleInstance {
1483 module_instance: module_instance.clone(),
1484 },
1485 );
1486 };
1487
1488 Ok(super::lex_cache::with_transform_lex_cache(|| {
1489 execute_transform_passes_on_source_with_active_lex_cache(
1490 source,
1491 dialect,
1492 requested,
1493 context,
1494 Some(closed_world_bundle),
1495 Some(reachability_precision),
1496 TransformExecutionRuntimePolicyV0 {
1497 verification: execution_policy,
1498 semantic_trust_recording: TransformSemanticTrustRecordingV0::Record,
1499 module_qualified_symbols: Some(module_qualified_symbols),
1500 retained_class_names,
1501 token_ownership_census,
1502 token_ownership_module_instance,
1503 },
1504 )
1505 }))
1506}
1507
1508#[doc(hidden)]
1509pub fn execute_transform_passes_on_source_with_dialect_and_context_without_lex_cache_for_measurement(
1510 source: &str,
1511 dialect: StyleDialect,
1512 requested: &[TransformPassKind],
1513 context: &TransformExecutionContextV0,
1514) -> TransformExecutionSummaryV0 {
1515 execute_transform_passes_on_source_with_active_lex_cache(
1516 source,
1517 dialect,
1518 requested,
1519 context,
1520 None,
1521 None,
1522 TransformExecutionRuntimePolicyV0 {
1523 verification: &TransformExecutionPolicyV0::default(),
1524 semantic_trust_recording: TransformSemanticTrustRecordingV0::Record,
1525 module_qualified_symbols: None,
1526 retained_class_names: &[],
1527 token_ownership_census: None,
1528 token_ownership_module_instance: None,
1529 },
1530 )
1531}
1532
1533#[doc(hidden)]
1534pub fn execute_transform_passes_on_source_with_dialect_and_context_without_semantic_trust_for_measurement(
1535 source: &str,
1536 dialect: StyleDialect,
1537 requested: &[TransformPassKind],
1538 context: &TransformExecutionContextV0,
1539) -> TransformExecutionSummaryV0 {
1540 super::lex_cache::with_transform_lex_cache(|| {
1541 execute_transform_passes_on_source_with_active_lex_cache(
1542 source,
1543 dialect,
1544 requested,
1545 context,
1546 None,
1547 None,
1548 TransformExecutionRuntimePolicyV0 {
1549 verification: &TransformExecutionPolicyV0::default(),
1550 semantic_trust_recording: TransformSemanticTrustRecordingV0::OmitForMeasurement,
1551 module_qualified_symbols: None,
1552 retained_class_names: &[],
1553 token_ownership_census: None,
1554 token_ownership_module_instance: None,
1555 },
1556 )
1557 })
1558}
1559
1560fn dispatch_text_local_pass(
1561 pass_id: &'static str,
1562 handler: TransformTextLocalPassHandlerV0,
1563 current_ir: &TransformIrV0,
1564 dialect: StyleDialect,
1565 context: &TransformExecutionContextV0,
1566) -> Option<TransformPassDispatchResultV0> {
1567 debug_assert_eq!(
1568 omena_transform_cst::transform_pass_class(handler.kind),
1569 TransformPassClassV0::TextLocal
1570 );
1571 let input = TransformTextLocalPassInputV0::from_ir(
1572 current_ir,
1573 handler.window_scope,
1574 handler.execution_mode,
1575 dialect,
1576 context,
1577 );
1578 let output = (handler.run)(input);
1579 let input_byte_len = output.input_byte_len();
1580 let mutation_count = output.mutation_count;
1581 let provenance_mutation_spans = output.provenance_mutation_spans().to_vec();
1582 let next_css = output.into_document_css();
1583 let mut result = TransformPassDispatchResultV0::textual_mutation(
1584 pass_id,
1585 input_byte_len,
1586 next_css,
1587 mutation_count,
1588 handler.detail,
1589 );
1590 if !provenance_mutation_spans.is_empty() {
1591 result.provenance_mutation_spans = Some(provenance_mutation_spans);
1592 }
1593 Some(result)
1594}
1595
1596fn run_whitespace_strip_text_local(
1597 input: TransformTextLocalPassInputV0<'_>,
1598) -> TransformTextLocalPassOutputV0<'_> {
1599 input.rewrite_text_local(|source, dialect, _context| normalize_css_whitespace(source, dialect))
1600}
1601
1602fn run_comment_strip_text_local(
1603 input: TransformTextLocalPassInputV0<'_>,
1604) -> TransformTextLocalPassOutputV0<'_> {
1605 input.rewrite_text_local(|source, dialect, _context| strip_css_comments(source, dialect))
1606}
1607
1608fn run_number_compression_text_local(
1609 input: TransformTextLocalPassInputV0<'_>,
1610) -> TransformTextLocalPassOutputV0<'_> {
1611 input.rewrite_text_local(|source, dialect, _context| compress_css_numbers(source, dialect))
1612}
1613
1614fn run_unit_normalization_text_local(
1615 input: TransformTextLocalPassInputV0<'_>,
1616) -> TransformTextLocalPassOutputV0<'_> {
1617 input.rewrite_text_local(|source, dialect, _context| normalize_css_units(source, dialect))
1618}
1619
1620fn run_color_compression_text_local(
1621 input: TransformTextLocalPassInputV0<'_>,
1622) -> TransformTextLocalPassOutputV0<'_> {
1623 input.rewrite_text_local(|source, dialect, _context| compress_css_colors(source, dialect))
1624}
1625
1626fn run_url_quote_strip_text_local(
1627 input: TransformTextLocalPassInputV0<'_>,
1628) -> TransformTextLocalPassOutputV0<'_> {
1629 input.rewrite_text_local(|source, dialect, _context| strip_css_url_quotes(source, dialect))
1630}
1631
1632fn run_string_quote_normalize_text_local(
1633 input: TransformTextLocalPassInputV0<'_>,
1634) -> TransformTextLocalPassOutputV0<'_> {
1635 input.rewrite_text_local(|source, dialect, _context| {
1636 normalize_css_string_quotes(source, dialect)
1637 })
1638}
1639
1640fn run_selector_is_where_compression_text_local(
1641 input: TransformTextLocalPassInputV0<'_>,
1642) -> TransformTextLocalPassOutputV0<'_> {
1643 input.rewrite_text_local(|source, dialect, _context| {
1644 compress_css_is_where_selectors(source, dialect)
1645 })
1646}
1647
1648fn run_shorthand_combining_text_local(
1649 input: TransformTextLocalPassInputV0<'_>,
1650) -> TransformTextLocalPassOutputV0<'_> {
1651 input.rewrite_text_local(|source, dialect, _context| combine_css_shorthands(source, dialect))
1652}
1653
1654fn run_vendor_prefixing_text_local(
1655 input: TransformTextLocalPassInputV0<'_>,
1656) -> TransformTextLocalPassOutputV0<'_> {
1657 input.rewrite_text_local(|source, dialect, context| {
1658 let vendor_prefix_policy = context
1659 .vendor_prefix_policy
1660 .unwrap_or_else(TransformVendorPrefixPolicyV0::conservative);
1661 add_css_vendor_prefixes(source, dialect, vendor_prefix_policy)
1662 })
1663}
1664
1665fn run_stale_prefix_removal_text_local(
1666 input: TransformTextLocalPassInputV0<'_>,
1667) -> TransformTextLocalPassOutputV0<'_> {
1668 input.rewrite_text_local(|source, dialect, _context| {
1669 remove_stale_css_vendor_prefixes(source, dialect)
1670 })
1671}
1672
1673fn run_light_dark_lowering_text_local(
1674 input: TransformTextLocalPassInputV0<'_>,
1675) -> TransformTextLocalPassOutputV0<'_> {
1676 input.rewrite_text_local(|source, dialect, _context| lower_css_light_dark(source, dialect))
1677}
1678
1679fn run_color_mix_lowering_text_local(
1680 input: TransformTextLocalPassInputV0<'_>,
1681) -> TransformTextLocalPassOutputV0<'_> {
1682 input.rewrite_text_local(|source, dialect, _context| lower_css_color_mix(source, dialect))
1683}
1684
1685fn run_oklch_oklab_lowering_text_local(
1686 input: TransformTextLocalPassInputV0<'_>,
1687) -> TransformTextLocalPassOutputV0<'_> {
1688 input.rewrite_text_local(|source, dialect, _context| lower_css_oklab_oklch(source, dialect))
1689}
1690
1691fn run_color_function_lowering_text_local(
1692 input: TransformTextLocalPassInputV0<'_>,
1693) -> TransformTextLocalPassOutputV0<'_> {
1694 input.rewrite_text_local(|source, dialect, _context| lower_css_color_function(source, dialect))
1695}
1696
1697fn run_relative_color_lowering_text_local(
1698 input: TransformTextLocalPassInputV0<'_>,
1699) -> TransformTextLocalPassOutputV0<'_> {
1700 input.rewrite_text_local(|source, dialect, _context| lower_relative_color(source, dialect))
1701}
1702
1703fn run_logical_to_physical_text_local(
1704 input: TransformTextLocalPassInputV0<'_>,
1705) -> TransformTextLocalPassOutputV0<'_> {
1706 input.rewrite_text_local(|source, dialect, _context| {
1707 lower_css_logical_to_physical(source, dialect)
1708 })
1709}
1710
1711fn run_value_resolution_text_local(
1712 input: TransformTextLocalPassInputV0<'_>,
1713) -> TransformTextLocalPassOutputV0<'_> {
1714 input.rewrite_text_local(|source, dialect, context| {
1715 resolve_static_css_modules_values(source, dialect, &context.css_module_value_resolutions)
1716 })
1717}
1718
1719fn run_static_var_substitution_text_local(
1720 input: TransformTextLocalPassInputV0<'_>,
1721) -> TransformTextLocalPassOutputV0<'_> {
1722 input.rewrite_text_local(|source, dialect, _context| {
1723 substitute_static_css_custom_properties(source, dialect)
1724 })
1725}
1726
1727fn run_calc_reduction_text_local(
1728 input: TransformTextLocalPassInputV0<'_>,
1729) -> TransformTextLocalPassOutputV0<'_> {
1730 input.rewrite_text_local(|source, dialect, _context| reduce_css_calc(source, dialect))
1731}
1732
1733fn dispatch_module_evaluation_pass(
1734 pass_id: &'static str,
1735 pass: TransformPassKind,
1736 input_css: &str,
1737 dialect: StyleDialect,
1738 context: &TransformExecutionContextV0,
1739) -> Option<TransformPassDispatchResultV0> {
1740 let input_byte_len = input_css.len();
1741 match pass {
1742 TransformPassKind::ScssModuleEvaluate
1743 if matches!(dialect, StyleDialect::Scss | StyleDialect::Sass) =>
1744 {
1745 if let Some(evaluation) = context.scss_module_evaluation.as_ref() {
1746 let materialized = materialize_transform_module_evaluation_output(
1747 input_css,
1748 evaluation,
1749 "applied explicit SCSS module evaluation native edit output from the evaluator boundary",
1750 "preserved SCSS source because native evaluator edits did not match the oracle boundary",
1751 );
1752 let mutation_count = usize::from(input_css != materialized.css);
1753 let mut result = TransformPassDispatchResultV0::textual_mutation(
1754 pass_id,
1755 input_byte_len,
1756 materialized.css,
1757 mutation_count,
1758 materialized.detail,
1759 );
1760 result.css_module_evaluation = Some(evaluation.clone());
1761 Some(result)
1762 } else {
1763 Some(TransformPassDispatchResultV0::blocked(
1764 pass_id,
1765 input_byte_len,
1766 TransformBlockedReasonV0::MissingPrecondition {
1767 precondition: TransformPreconditionV0::EvaluatorOutput {
1768 profile: TransformEvaluationProfileV0::Scss,
1769 },
1770 },
1771 "requires explicit SCSS evaluator output before mutation",
1772 ))
1773 }
1774 }
1775 TransformPassKind::ScssModuleEvaluate => Some(TransformPassDispatchResultV0::profile_only(
1776 pass_id,
1777 input_byte_len,
1778 TransformEvaluationProfileV0::Scss,
1779 "requires explicit SCSS evaluator output before mutation",
1780 )),
1781 TransformPassKind::LessModuleEvaluate if dialect == StyleDialect::Less => {
1782 if let Some(evaluation) = context.less_module_evaluation.as_ref() {
1783 let materialized = materialize_transform_module_evaluation_output(
1784 input_css,
1785 evaluation,
1786 "applied explicit Less module evaluation native edit output from the evaluator boundary",
1787 "preserved Less source because native evaluator edits did not match the oracle boundary",
1788 );
1789 let mutation_count = usize::from(input_css != materialized.css);
1790 let mut result = TransformPassDispatchResultV0::textual_mutation(
1791 pass_id,
1792 input_byte_len,
1793 materialized.css,
1794 mutation_count,
1795 materialized.detail,
1796 );
1797 result.css_module_evaluation = Some(evaluation.clone());
1798 Some(result)
1799 } else {
1800 Some(TransformPassDispatchResultV0::blocked(
1801 pass_id,
1802 input_byte_len,
1803 TransformBlockedReasonV0::MissingPrecondition {
1804 precondition: TransformPreconditionV0::EvaluatorOutput {
1805 profile: TransformEvaluationProfileV0::Less,
1806 },
1807 },
1808 "requires explicit Less evaluator output before mutation",
1809 ))
1810 }
1811 }
1812 TransformPassKind::LessModuleEvaluate => Some(TransformPassDispatchResultV0::profile_only(
1813 pass_id,
1814 input_byte_len,
1815 TransformEvaluationProfileV0::Less,
1816 "requires explicit Less evaluator output before mutation",
1817 )),
1818 _ => None,
1819 }
1820}
1821
1822fn dispatch_structural_pass(
1823 pass_id: &'static str,
1824 handler: TransformStructuralPassHandlerV0,
1825 current_ir: &mut TransformIrV0,
1826 context: TransformStructuralDispatchContextV0<'_>,
1827) -> Option<TransformPassDispatchResultV0> {
1828 debug_assert_eq!(
1829 omena_transform_cst::transform_pass_class(handler.kind),
1830 TransformPassClassV0::Structural
1831 );
1832 let decision_policy = transform_structural_decision_policy(handler.kind)?;
1833 let input_byte_len = current_ir.source_text().len();
1834 reset_structural_ir_transaction_mutation_span_batches();
1835 let mut result = (handler.run)(TransformStructuralPassInputV0 {
1836 pass_id,
1837 kind: handler.kind,
1838 decision_policy,
1839 reachability_precision: classify_transform_reachability_precision(
1840 context.execution,
1841 context.closed_world_bundle.is_some(),
1842 context.reachability_precision_ceiling,
1843 ),
1844 current_ir,
1845 input_byte_len,
1846 dialect: context.dialect,
1847 context: context.execution,
1848 closed_world_bundle: context.closed_world_bundle,
1849 module_qualified_symbols: context.module_qualified_symbols,
1850 module_reachable_class_names: context.module_reachable_class_names,
1851 });
1852 let span_batches = take_structural_ir_transaction_mutation_span_batches();
1853 if let Some(mutation_spans) = compose_ir_transaction_mutation_span_batches(
1854 input_byte_len,
1855 current_ir.source_text().len(),
1856 span_batches.as_slice(),
1857 ) {
1858 result.provenance_mutation_spans = Some(mutation_spans);
1859 }
1860 Some(result)
1861}
1862
1863#[derive(Clone, Copy)]
1864struct TransformStructuralDispatchContextV0<'a> {
1865 dialect: StyleDialect,
1866 execution: &'a TransformExecutionContextV0,
1867 closed_world_bundle: Option<&'a ClosedWorldBundleV0>,
1868 module_qualified_symbols: Option<&'a ModuleQualifiedSymbolSetV0>,
1869 module_reachable_class_names: Option<&'a [String]>,
1870 reachability_precision_ceiling: Option<FactPrecision>,
1871}
1872
1873pub fn classify_transform_reachability_precision(
1874 context: &TransformExecutionContextV0,
1875 closed_world_bundle_available: bool,
1876 precision_ceiling: Option<FactPrecision>,
1877) -> FactPrecision {
1878 let observed = if closed_world_bundle_available {
1879 if precision_ceiling == Some(FactPrecision::Exact) {
1881 FactPrecision::Exact
1882 } else {
1883 FactPrecision::Conservative
1884 }
1885 } else if !context.reachable_class_names.is_empty()
1886 || !context.reachable_keyframe_names.is_empty()
1887 || !context.reachable_value_names.is_empty()
1888 || !context.reachable_custom_property_names.is_empty()
1889 {
1890 FactPrecision::Heuristic
1891 } else {
1892 FactPrecision::Unknown
1893 };
1894 precision_ceiling.map_or(observed, |ceiling| observed.bounded_by(ceiling))
1895}
1896
1897fn dispatch_emission_pass(
1898 pass_id: &'static str,
1899 pass: TransformPassKind,
1900 input_byte_len: usize,
1901) -> Option<TransformPassDispatchResultV0> {
1902 match pass {
1903 TransformPassKind::PrintCss => Some(TransformPassDispatchResultV0::no_change(
1904 pass_id,
1905 input_byte_len,
1906 TransformNoChangeReasonV0::EmissionBoundary,
1907 "observed final emission boundary",
1908 )),
1909 _ => None,
1910 }
1911}
1912
1913fn run_import_inline_structural(
1914 mut input: TransformStructuralPassInputV0<'_>,
1915) -> TransformPassDispatchResultV0 {
1916 if input.dialect != StyleDialect::Less && input.context.import_inlines.is_empty() {
1917 return TransformPassDispatchResultV0::blocked(
1918 input.pass_id,
1919 input.input_byte_len,
1920 TransformBlockedReasonV0::MissingPrecondition {
1921 precondition: TransformPreconditionV0::ResolvedImportReplacements,
1922 },
1923 "requires explicit resolved import replacements before mutation",
1924 );
1925 }
1926 let dialect = input.dialect;
1927 let import_inlines = input.context.import_inlines.clone();
1928 let Ok(mutation_count) =
1929 inline_css_imports_in_ir(input.current_ir_mut(), dialect, &import_inlines)
1930 else {
1931 return input
1932 .ir_rejected("typed IR transaction rejected the import-inline structural rewrite");
1933 };
1934 let mut result = input.ir_mutation_result(
1935 mutation_count,
1936 "replaced resolved @import directives and optional Less imports",
1937 );
1938 result.css_import_inlines = import_inlines;
1939 result
1940}
1941
1942fn run_resolve_css_modules_composes_structural(
1943 mut input: TransformStructuralPassInputV0<'_>,
1944) -> TransformPassDispatchResultV0 {
1945 let dialect = input.dialect;
1946 let explicit_resolutions = input.context.css_module_composes_resolutions.clone();
1947 let resolutions =
1948 css_module_composes_resolutions_for_ir(input.current_ir, dialect, &explicit_resolutions);
1949 if resolutions.is_empty() {
1950 return TransformPassDispatchResultV0::blocked(
1951 input.pass_id,
1952 input.input_byte_len,
1953 TransformBlockedReasonV0::MissingPrecondition {
1954 precondition: TransformPreconditionV0::CssModulesComposesResolution,
1955 },
1956 "requires CSS Modules composes declarations or an explicit export set before mutation",
1957 );
1958 }
1959 let Ok(mutation_count) =
1960 resolve_css_module_composes_in_ir(input.current_ir_mut(), dialect, &resolutions)
1961 else {
1962 return input.ir_rejected(
1963 "typed IR transaction rejected the CSS Modules composes structural rewrite",
1964 );
1965 };
1966 let mut result = input.ir_mutation_result(
1967 mutation_count,
1968 "removed resolved CSS Modules composes declarations using an explicit export set",
1969 );
1970 result.css_module_composes_exports = resolutions;
1971 result
1972}
1973
1974fn run_design_token_routing_structural(
1975 mut input: TransformStructuralPassInputV0<'_>,
1976) -> TransformPassDispatchResultV0 {
1977 if input.context.design_token_routes.is_empty() {
1978 return TransformPassDispatchResultV0::blocked(
1979 input.pass_id,
1980 input.input_byte_len,
1981 TransformBlockedReasonV0::MissingPrecondition {
1982 precondition: TransformPreconditionV0::DesignTokenRoutes,
1983 },
1984 "requires explicit bridge design-token routes before mutation",
1985 );
1986 }
1987 let dialect = input.dialect;
1988 let design_token_routes = input.context.design_token_routes.clone();
1989 let Ok(mutation_count) =
1990 route_design_token_values_in_ir(input.current_ir_mut(), dialect, &design_token_routes)
1991 else {
1992 return input
1993 .ir_rejected("typed IR transaction rejected the design-token structural rewrite");
1994 };
1995 let mut result = input.ir_mutation_result(
1996 mutation_count,
1997 "routed whole-value design-token references through explicit bridge token routes",
1998 );
1999 result.design_token_routes = design_token_routes;
2000 result
2001}
2002
2003fn run_hash_css_module_class_names_structural(
2004 mut input: TransformStructuralPassInputV0<'_>,
2005) -> TransformPassDispatchResultV0 {
2006 let dialect = input.dialect;
2007 let class_name_rewrites = input.context.class_name_rewrites.clone();
2008 let Ok(mutation_count) =
2009 rewrite_css_module_class_names_in_ir(input.current_ir_mut(), dialect, &class_name_rewrites)
2010 else {
2011 return input.ir_rejected(
2012 "typed IR transaction rejected the CSS Modules class hashing structural rewrite",
2013 );
2014 };
2015 if mutation_count == 0 && class_name_rewrites.is_empty() {
2016 return TransformPassDispatchResultV0::blocked(
2017 input.pass_id,
2018 input.input_byte_len,
2019 TransformBlockedReasonV0::MissingPrecondition {
2020 precondition: TransformPreconditionV0::SelectorIdentity,
2021 },
2022 "requires an explicit selector identity map or CSS Modules scope markers before mutation",
2023 );
2024 }
2025 if mutation_count == 0 {
2026 return TransformPassDispatchResultV0::no_change(
2027 input.pass_id,
2028 input.input_byte_len,
2029 TransformNoChangeReasonV0::NoMatchingSelectorRewrite,
2030 "observed CSS Modules class hashing boundary without matching selector rewrites",
2031 );
2032 }
2033 input.ir_mutation_result(
2034 mutation_count,
2035 "rewrote CSS Modules class selectors and scope markers through the structural IR boundary",
2036 )
2037}
2038
2039fn run_rule_deduplication_structural(
2040 mut input: TransformStructuralPassInputV0<'_>,
2041) -> TransformPassDispatchResultV0 {
2042 let dialect = input.dialect;
2043 let mutation_count = match dedupe_exact_css_rules_in_ir(input.current_ir_mut(), dialect) {
2044 Ok(result) => result,
2045 Err(_) => {
2046 return input
2047 .ir_rejected("typed IR transaction rejected the rule deduplication rewrite");
2048 }
2049 };
2050 input.ir_mutation_result(
2051 mutation_count,
2052 "removed cascade-safe duplicate ordinary rules while preserving the final occurrence",
2053 )
2054}
2055
2056fn run_rule_merging_structural(
2057 mut input: TransformStructuralPassInputV0<'_>,
2058) -> TransformPassDispatchResultV0 {
2059 let dialect = input.dialect;
2060 let Ok(mutation_count) =
2061 merge_adjacent_same_selector_css_rules_in_ir(input.current_ir_mut(), dialect)
2062 else {
2063 return input.ir_rejected("typed IR transaction rejected the rule merging rewrite");
2064 };
2065 input.ir_mutation_result(
2066 mutation_count,
2067 "merged adjacent same-selector ordinary rule runs without reordering declarations",
2068 )
2069}
2070
2071fn run_selector_merging_structural(
2072 mut input: TransformStructuralPassInputV0<'_>,
2073) -> TransformPassDispatchResultV0 {
2074 let dialect = input.dialect;
2075 let Ok(mutation_count) =
2076 merge_adjacent_same_block_css_selectors_in_ir(input.current_ir_mut(), dialect)
2077 else {
2078 return input.ir_rejected("typed IR transaction rejected the selector merging rewrite");
2079 };
2080 input.ir_mutation_result(
2081 mutation_count,
2082 "merged adjacent ordinary rule runs with identical declaration blocks",
2083 )
2084}
2085
2086fn run_nesting_unwrap_structural(
2087 mut input: TransformStructuralPassInputV0<'_>,
2088) -> TransformPassDispatchResultV0 {
2089 let dialect = input.dialect;
2090 let Ok(mutation_count) = unwrap_css_nesting_in_ir(input.current_ir_mut(), dialect) else {
2091 return input.ir_rejected("typed IR transaction rejected the nesting structural rewrite");
2092 };
2093 input.ir_mutation_result(
2094 mutation_count,
2095 "unwrapped nested ordinary rules and conditional group rules",
2096 )
2097}
2098
2099fn run_scope_flatten_structural(
2100 mut input: TransformStructuralPassInputV0<'_>,
2101) -> TransformPassDispatchResultV0 {
2102 let dialect = input.dialect;
2103 let Ok(mutation_count) = flatten_css_scopes_in_ir(input.current_ir_mut(), dialect) else {
2104 return input.ir_rejected("typed IR transaction rejected the scope structural rewrite");
2105 };
2106 input.ir_mutation_result(
2107 mutation_count,
2108 "flattened only @scope candidates accepted by the cascade scope-flatten proof",
2109 )
2110}
2111
2112fn run_layer_flatten_structural(
2113 mut input: TransformStructuralPassInputV0<'_>,
2114) -> TransformPassDispatchResultV0 {
2115 if input.closed_world_bundle().is_some() {
2116 let dialect = input.dialect;
2117 let Ok(mutation_count) = flatten_css_layers_in_ir(input.current_ir_mut(), dialect, true)
2118 else {
2119 return input.ir_rejected("typed IR transaction rejected the layer structural rewrite");
2120 };
2121 input.ir_mutation_result(
2122 mutation_count,
2123 "flattened only @layer candidates accepted by the closed-bundle cascade proof",
2124 )
2125 } else {
2126 TransformPassDispatchResultV0::blocked(
2127 input.pass_id,
2128 input.input_byte_len,
2129 TransformBlockedReasonV0::MissingPrecondition {
2130 precondition: TransformPreconditionV0::ClosedStyleWorldBundle,
2131 },
2132 "requires an explicit closed-style-world bundle witness before mutation",
2133 )
2134 }
2135}
2136
2137fn run_supports_static_eval_structural(
2138 mut input: TransformStructuralPassInputV0<'_>,
2139) -> TransformPassDispatchResultV0 {
2140 let dialect = input.dialect;
2141 let assumption = static_supports_assumption_from_context(input.context);
2142 let Ok(mutation_count) =
2143 evaluate_static_supports_rules_in_ir(input.current_ir_mut(), dialect, assumption)
2144 else {
2145 return input
2146 .ir_rejected("typed IR transaction rejected the supports static structural rewrite");
2147 };
2148 input.ir_mutation_result(
2149 mutation_count,
2150 "evaluated simple @supports branches with cascade supports-static witness",
2151 )
2152}
2153
2154fn static_supports_assumption_from_context(
2155 context: &TransformExecutionContextV0,
2156) -> StaticSupportsAssumptionV0 {
2157 context
2158 .supports_target_capability
2159 .map(StaticSupportsAssumptionV0::TargetCapability)
2160 .unwrap_or(StaticSupportsAssumptionV0::ModernBrowser)
2161}
2162
2163fn run_media_static_eval_structural(
2164 mut input: TransformStructuralPassInputV0<'_>,
2165) -> TransformPassDispatchResultV0 {
2166 let dialect = input.dialect;
2167 let Ok(mutation_count) = evaluate_static_media_rules_in_ir(input.current_ir_mut(), dialect)
2168 else {
2169 return input
2170 .ir_rejected("typed IR transaction rejected the media static structural rewrite");
2171 };
2172 input.ir_mutation_result(
2173 mutation_count,
2174 "evaluated literal @media all/not all branches and normalized simple min/max media ranges",
2175 )
2176}
2177
2178fn run_container_static_eval_structural(
2179 mut input: TransformStructuralPassInputV0<'_>,
2180) -> TransformPassDispatchResultV0 {
2181 let dialect = input.dialect;
2182 let Ok(mutation_count) = evaluate_static_container_rules_in_ir(input.current_ir_mut(), dialect)
2183 else {
2184 return input
2185 .ir_rejected("typed IR transaction rejected the container static structural rewrite");
2186 };
2187 input.ir_mutation_result(
2188 mutation_count,
2189 "removed @container branches whose size condition is provably unsatisfiable",
2190 )
2191}
2192
2193fn run_native_css_static_eval_structural(
2194 mut input: TransformStructuralPassInputV0<'_>,
2195) -> TransformPassDispatchResultV0 {
2196 if input.dialect == StyleDialect::Css {
2197 let dialect = input.dialect;
2198 let Ok(mutation_count) =
2199 evaluate_native_css_static_values_in_ir(input.current_ir_mut(), dialect)
2200 else {
2201 return input.ir_rejected(
2202 "typed IR transaction rejected the native CSS static structural rewrite",
2203 );
2204 };
2205 input.ir_mutation_result(
2206 mutation_count,
2207 "folded fully static native CSS if() values and native CSS function calls while preserving runtime-dependent constructs",
2208 )
2209 } else {
2210 TransformPassDispatchResultV0::no_change(
2211 input.pass_id,
2212 input.input_byte_len,
2213 TransformNoChangeReasonV0::DialectNotApplicable,
2214 "preserved non-CSS dialect because native CSS static evaluation is CSS-only",
2215 )
2216 }
2217}
2218
2219fn run_dead_media_branch_removal_structural(
2220 mut input: TransformStructuralPassInputV0<'_>,
2221) -> TransformPassDispatchResultV0 {
2222 let dialect = input.dialect;
2223 let drop_dark_mode_media_queries = input.context.drop_dark_mode_media_queries;
2224 let Ok(mutation_count) = evaluate_dead_media_branch_rules_in_ir(
2225 input.current_ir_mut(),
2226 dialect,
2227 drop_dark_mode_media_queries,
2228 ) else {
2229 return input
2230 .ir_rejected("typed IR transaction rejected the dead media structural rewrite");
2231 };
2232 input.ir_mutation_result(
2233 mutation_count,
2234 "removed dead @media branches through the static cascade witness evaluator",
2235 )
2236}
2237
2238fn run_dead_supports_branch_removal_structural(
2239 mut input: TransformStructuralPassInputV0<'_>,
2240) -> TransformPassDispatchResultV0 {
2241 let dialect = input.dialect;
2242 let assumption = static_supports_assumption_from_context(input.context);
2243 let Ok(mutation_count) =
2244 evaluate_static_supports_rules_in_ir(input.current_ir_mut(), dialect, assumption)
2245 else {
2246 return input
2247 .ir_rejected("typed IR transaction rejected the dead supports structural rewrite");
2248 };
2249 input.ir_mutation_result(
2250 mutation_count,
2251 "removed dead @supports branches through the static cascade witness evaluator",
2252 )
2253}
2254
2255fn run_tree_shake_class_structural(
2256 mut input: TransformStructuralPassInputV0<'_>,
2257) -> TransformPassDispatchResultV0 {
2258 if let Some(reason) = input.precision_blocker() {
2259 return TransformPassDispatchResultV0::blocked(
2260 input.pass_id,
2261 input.input_byte_len,
2262 reason,
2263 "requires an explicit closed-world bundle before mutation",
2264 );
2265 }
2266 let Some(bundle) = input.closed_world_bundle() else {
2267 return TransformPassDispatchResultV0::blocked(
2268 input.pass_id,
2269 input.input_byte_len,
2270 TransformBlockedReasonV0::MissingPrecondition {
2271 precondition: TransformPreconditionV0::ClosedWorldBundle,
2272 },
2273 "requires an explicit closed-world bundle before mutation",
2274 );
2275 };
2276 let dialect = input.dialect;
2277 let reachable_class_names = input.module_reachable_class_names.map_or_else(
2278 || bundle.reachability().class_names().to_vec(),
2279 <[String]>::to_vec,
2280 );
2281 let Ok(removals) =
2282 tree_shake_css_class_rules_in_ir(input.current_ir_mut(), dialect, &reachable_class_names)
2283 else {
2284 return input
2285 .ir_rejected("typed IR transaction rejected the class tree-shake structural rewrite");
2286 };
2287 let mutation_count = removals.len();
2288 let mut result = input.ir_mutation_result(
2289 mutation_count,
2290 "removed unreachable class-owned selector rules under an explicit closed-world bundle",
2291 );
2292 result.semantic_removals = removals
2293 .into_iter()
2294 .map(|removal| removal.into_public(input.pass_id))
2295 .collect();
2296 result
2297}
2298
2299fn run_tree_shake_keyframes_structural(
2300 mut input: TransformStructuralPassInputV0<'_>,
2301) -> TransformPassDispatchResultV0 {
2302 if let Some(reason) = input.precision_blocker() {
2303 return TransformPassDispatchResultV0::blocked(
2304 input.pass_id,
2305 input.input_byte_len,
2306 reason,
2307 "requires an explicit closed-world bundle before mutation",
2308 );
2309 }
2310 let Some(bundle) = input.closed_world_bundle() else {
2311 return TransformPassDispatchResultV0::blocked(
2312 input.pass_id,
2313 input.input_byte_len,
2314 TransformBlockedReasonV0::MissingPrecondition {
2315 precondition: TransformPreconditionV0::ClosedWorldBundle,
2316 },
2317 "requires an explicit closed-world bundle before mutation",
2318 );
2319 };
2320 let dialect = input.dialect;
2321 let reachable_keyframe_names = input.module_qualified_symbols.map_or_else(
2322 || bundle.reachability().keyframe_names().to_vec(),
2323 |symbols| symbols.keyframe_names().to_vec(),
2324 );
2325 let reachable_class_names = input.module_reachable_class_names.map_or_else(
2326 || bundle.reachability().class_names().to_vec(),
2327 <[String]>::to_vec,
2328 );
2329 let Ok(removals) = tree_shake_css_keyframes_in_ir(
2330 input.current_ir_mut(),
2331 dialect,
2332 &reachable_keyframe_names,
2333 &reachable_class_names,
2334 ) else {
2335 return input.ir_rejected(
2336 "typed IR transaction rejected the keyframes tree-shake structural rewrite",
2337 );
2338 };
2339 let mutation_count = removals.len();
2340 let mut result = input.ir_mutation_result(
2341 mutation_count,
2342 "removed unreferenced @keyframes under an explicit closed-world bundle",
2343 );
2344 result.semantic_removals = removals
2345 .into_iter()
2346 .map(|removal| removal.into_public(input.pass_id))
2347 .collect();
2348 result
2349}
2350
2351fn run_tree_shake_value_structural(
2352 mut input: TransformStructuralPassInputV0<'_>,
2353) -> TransformPassDispatchResultV0 {
2354 if let Some(reason) = input.precision_blocker() {
2355 return TransformPassDispatchResultV0::blocked(
2356 input.pass_id,
2357 input.input_byte_len,
2358 reason,
2359 "requires an explicit closed-world bundle before mutation",
2360 );
2361 }
2362 let Some(bundle) = input.closed_world_bundle() else {
2363 return TransformPassDispatchResultV0::blocked(
2364 input.pass_id,
2365 input.input_byte_len,
2366 TransformBlockedReasonV0::MissingPrecondition {
2367 precondition: TransformPreconditionV0::ClosedWorldBundle,
2368 },
2369 "requires an explicit closed-world bundle before mutation",
2370 );
2371 };
2372 let dialect = input.dialect;
2373 let reachable_value_names = input.module_qualified_symbols.map_or_else(
2374 || bundle.reachability().value_names().to_vec(),
2375 |symbols| symbols.value_names().to_vec(),
2376 );
2377 let reachable_keyframe_names = input.module_qualified_symbols.map_or_else(
2378 || bundle.reachability().keyframe_names().to_vec(),
2379 |symbols| symbols.keyframe_names().to_vec(),
2380 );
2381 let reachable_class_names = input.module_reachable_class_names.map_or_else(
2382 || bundle.reachability().class_names().to_vec(),
2383 <[String]>::to_vec,
2384 );
2385 let Ok(removals) = tree_shake_css_modules_values_in_ir(
2386 input.current_ir_mut(),
2387 dialect,
2388 &reachable_value_names,
2389 &reachable_keyframe_names,
2390 &reachable_class_names,
2391 ) else {
2392 return input.ir_rejected(
2393 "typed IR transaction rejected the CSS Modules value tree-shake structural rewrite",
2394 );
2395 };
2396 let mutation_count = removals.len();
2397 let mut result = input.ir_mutation_result(
2398 mutation_count,
2399 "removed unreachable local CSS Modules @value declarations under an explicit closed-world bundle",
2400 );
2401 result.semantic_removals = removals
2402 .into_iter()
2403 .map(|removal| removal.into_public(input.pass_id))
2404 .collect();
2405 result
2406}
2407
2408fn run_tree_shake_custom_property_structural(
2409 mut input: TransformStructuralPassInputV0<'_>,
2410) -> TransformPassDispatchResultV0 {
2411 if let Some(reason) = input.precision_blocker() {
2412 return TransformPassDispatchResultV0::blocked(
2413 input.pass_id,
2414 input.input_byte_len,
2415 reason,
2416 "requires an explicit closed-world bundle before mutation",
2417 );
2418 }
2419 let Some(bundle) = input.closed_world_bundle() else {
2420 return TransformPassDispatchResultV0::blocked(
2421 input.pass_id,
2422 input.input_byte_len,
2423 TransformBlockedReasonV0::MissingPrecondition {
2424 precondition: TransformPreconditionV0::ClosedWorldBundle,
2425 },
2426 "requires an explicit closed-world bundle before mutation",
2427 );
2428 };
2429 let dialect = input.dialect;
2430 let reachable_custom_property_names = input.module_qualified_symbols.map_or_else(
2431 || bundle.reachability().custom_property_names().to_vec(),
2432 |symbols| symbols.custom_property_names().to_vec(),
2433 );
2434 let reachable_keyframe_names = input.module_qualified_symbols.map_or_else(
2435 || bundle.reachability().keyframe_names().to_vec(),
2436 |symbols| symbols.keyframe_names().to_vec(),
2437 );
2438 let reachable_class_names = input.module_reachable_class_names.map_or_else(
2439 || bundle.reachability().class_names().to_vec(),
2440 <[String]>::to_vec,
2441 );
2442 let Ok(removals) = tree_shake_css_custom_properties_in_ir(
2443 input.current_ir_mut(),
2444 dialect,
2445 &reachable_custom_property_names,
2446 &reachable_keyframe_names,
2447 &reachable_class_names,
2448 ) else {
2449 return input.ir_rejected(
2450 "typed IR transaction rejected the custom-property tree-shake structural rewrite",
2451 );
2452 };
2453 let mutation_count = removals.len();
2454 let mut result = input.ir_mutation_result(
2455 mutation_count,
2456 "removed unreachable custom-property declarations under an explicit closed-world bundle",
2457 );
2458 result.semantic_removals = removals
2459 .into_iter()
2460 .map(|removal| removal.into_public(input.pass_id))
2461 .collect();
2462 result
2463}
2464
2465fn run_empty_rule_removal_structural(
2466 mut input: TransformStructuralPassInputV0<'_>,
2467) -> TransformPassDispatchResultV0 {
2468 let dialect = input.dialect;
2469 let Ok(mutation_count) = remove_empty_css_rules_in_ir(input.current_ir_mut(), dialect) else {
2470 return input
2471 .ir_rejected("typed IR transaction rejected the empty-rule structural rewrite");
2472 };
2473 input.ir_mutation_result(
2474 mutation_count,
2475 "removed ordinary empty rules with no comments or at-rule semantics",
2476 )
2477}
2478
2479fn execute_transform_passes_on_source_with_active_lex_cache(
2480 source: &str,
2481 dialect: StyleDialect,
2482 requested: &[TransformPassKind],
2483 context: &TransformExecutionContextV0,
2484 explicit_closed_world_bundle: Option<&ClosedWorldBundleV0>,
2485 reachability_precision_ceiling: Option<FactPrecision>,
2486 runtime_policy: TransformExecutionRuntimePolicyV0<'_>,
2487) -> TransformExecutionSummaryV0 {
2488 reset_structural_ir_transaction_telemetry();
2489 let pass_plan = plan_transform_passes(requested);
2490 let pass_registry = default_transform_pass_registry();
2491 let stable_ir =
2492 build_stable_transform_ir_from_source(source, dialect, "omena-transform-passes.execution");
2493 let stable_ir_nodes = stable_ir.nodes;
2494 let mut coordinate_map = TransformSpanCoordinateMapV0::new(source.len());
2495 let requested_pass_ids = requested.iter().map(|pass| pass.id()).collect::<Vec<_>>();
2496 let ordered_pass_ids = pass_plan.ordered_pass_ids.clone();
2497 let mut document = TransformExecutionDocumentV0::new(source, dialect);
2498 let closed_world_bundle = explicit_closed_world_bundle;
2499 let module_qualified_symbols = runtime_policy.module_qualified_symbols;
2500 let module_reachable_class_names = module_qualified_symbols.map(|symbols| {
2501 let mut class_names = symbols.class_names().to_vec();
2502 class_names.extend(runtime_policy.retained_class_names.iter().cloned());
2503 class_names.sort();
2504 class_names.dedup();
2505 class_names
2506 });
2507 let mut decisions = Vec::new();
2508 let mut outcomes = Vec::new();
2509 let mut css_module_evaluation = None;
2510 let mut css_import_inlines = Vec::new();
2511 let mut css_module_composes_exports = Vec::new();
2512 let mut design_token_routes = Vec::new();
2513 let mut semantic_removals = Vec::new();
2514 let mut outcome_mutation_spans = Vec::new();
2515 let mut cascade_proof_obligations = Vec::new();
2516 let mut winner_equality_obligations = Vec::<TransformWinnerEqualityObligationV0>::new();
2517 let mut semantic_preservation_telemetry = TransformSemanticPreservationTelemetryV0::default();
2518 let strict_policy = runtime_policy.verification.strict_policy.as_ref();
2519 let semantic_trust_recording = runtime_policy.semantic_trust_recording;
2520 let strict_pass_ids = strict_policy
2521 .filter(|policy| policy.enforce_winner_equality)
2522 .map(|_| {
2523 strict_verification_build_profile()
2524 .pass_ids
2525 .into_iter()
2526 .collect::<BTreeSet<_>>()
2527 })
2528 .unwrap_or_default();
2529 let mut strict_policy_summary = strict_policy
2530 .map_or_else(TransformStrictPolicySummaryV0::default, |policy| {
2531 TransformStrictPolicySummaryV0::for_profile(policy.profile_id)
2532 });
2533 let mut closed_world_admission_summary = TransformClosedWorldAdmissionSummaryV0::default();
2534
2535 for (pass_index, pass_id) in ordered_pass_ids.iter().enumerate() {
2536 let should_maintain_document_lex_cache =
2537 next_document_lex_cache_consumer(ordered_pass_ids.as_slice(), pass_index)
2538 == Some(TransformTextLocalExecutionModeV0::FullDocument);
2539 let pass = transform_pass_kind_from_id(pass_id);
2540 let runtime_entry = pass
2541 .and_then(|kind| runtime_pass_entry_for_kind(kind, pass_registry.entries.as_slice()));
2542 let dispatch_kind = runtime_entry
2543 .as_ref()
2544 .map(|entry| entry.registry_entry.dispatch_kind);
2545 let input_byte_len = document.current_byte_len();
2546 let input_content_signature = transform_content_signature(document.current_css());
2547 let semantic_preservation_input_ir = pass
2548 .filter(|kind| semantic_preservation_applies(*kind))
2549 .map(|_| document.current_ir.clone());
2550 let semantic_preservation_projection = pass
2551 .filter(|kind| semantic_preservation_applies(*kind))
2552 .map(|kind| {
2553 SemanticObservationProjectionV0::for_pass_input(
2554 kind,
2555 &document.current_ir,
2556 dialect,
2557 closed_world_bundle,
2558 module_qualified_symbols,
2559 module_reachable_class_names.as_deref(),
2560 )
2561 })
2562 .unwrap_or_default();
2563 let mut textual_bridge = TransformTextualBridgeSnapshotV0::default();
2564 let pass_cascade_proof_obligations =
2565 if dispatch_kind == Some(TransformPassDispatchKindV0::StructuralIrTransaction) {
2566 collect_cascade_proof_obligations_for_ir_pass_input(
2567 pass_id,
2568 pass,
2569 &document.current_ir,
2570 context,
2571 closed_world_bundle,
2572 )
2573 } else {
2574 let pass_input_css = textual_bridge.materialize_current_css(&document);
2575 collect_cascade_proof_obligations_for_pass_input(
2576 pass_id,
2577 pass,
2578 pass_input_css,
2579 dialect,
2580 context,
2581 closed_world_bundle,
2582 )
2583 };
2584 let discharge_evidence =
2585 flatten_discharge_evidence(pass, pass_cascade_proof_obligations.as_slice());
2586 let flatten_precondition_failure =
2587 flatten_discharge_precondition_failure(pass, pass_cascade_proof_obligations.as_slice());
2588 cascade_proof_obligations.extend(pass_cascade_proof_obligations);
2589 let closed_world_precondition_failure = pass
2590 .filter(|pass| transform_pass_requires_closed_world_bundle(*pass))
2591 .filter(|_| closed_world_bundle.is_none())
2592 .map(|pass| {
2593 let observed = classify_transform_reachability_precision(
2594 context,
2595 closed_world_bundle.is_some(),
2596 reachability_precision_ceiling,
2597 );
2598 transform_structural_decision_policy(pass)
2599 .and_then(|policy| policy.required_precision())
2600 .filter(|required| !observed.satisfies(*required))
2601 .map_or(
2602 TransformBlockedReasonV0::MissingPrecondition {
2603 precondition: TransformPreconditionV0::ClosedWorldBundle,
2604 },
2605 |required| TransformBlockedReasonV0::PrecisionBelowFloor {
2606 required,
2607 observed,
2608 },
2609 )
2610 });
2611 let strict_refusal_reasons = pass
2612 .filter(|pass| strict_pass_ids.contains(pass.id()))
2613 .map(|pass| strict_plan_refusal_reasons(pass, context))
2614 .unwrap_or_default();
2615 let fact_consuming_pass = pass.is_some_and(|pass| {
2616 matches!(
2617 transform_structural_decision_policy(pass).map(|policy| policy.class),
2618 Some(TransformStructuralDecisionClassV0::FactConsuming { .. })
2619 )
2620 });
2621 let closed_world_admission_reasons = pass
2622 .filter(|_| fact_consuming_pass)
2623 .and_then(|pass_kind| {
2624 closed_world_bundle.map(|bundle| {
2625 let o3 = closed_world_admission_o3_reasons(bundle, module_qualified_symbols);
2626 let mut reasons = closed_world_admission_o1_reasons(
2627 runtime_policy.token_ownership_census,
2628 runtime_policy
2629 .token_ownership_module_instance
2630 .or(o3.module_instance),
2631 pass_kind,
2632 );
2633 reasons.extend(o3.reasons);
2634 reasons.extend(closed_world_admission_o2_reasons(
2635 bundle,
2636 module_qualified_symbols,
2637 module_reachable_class_names.as_deref(),
2638 pass_kind,
2639 ));
2640 reasons
2641 })
2642 })
2643 .unwrap_or_default();
2644 if fact_consuming_pass
2645 && closed_world_bundle.is_some_and(|bundle| {
2646 bundle.composes_scan_state() == ClosedWorldComposesScanStateV0::SourceSetOpen
2647 })
2648 {
2649 closed_world_admission_summary.evidence_scope = Some("sourceSetOpen");
2650 }
2651 let strict_refused_before_dispatch = !strict_refusal_reasons.is_empty();
2652 let mut dispatch_result = if !strict_refusal_reasons.is_empty() {
2653 strict_policy_summary.record_refusal(*pass_id, strict_refusal_reasons.clone());
2654 TransformPassDispatchResultV0::blocked(
2655 pass_id,
2656 input_byte_len,
2657 TransformBlockedReasonV0::StrictVerification {
2658 reasons: strict_refusal_reasons,
2659 },
2660 "strict verification evidence was unavailable before dispatch",
2661 )
2662 } else if !closed_world_admission_reasons.is_empty() {
2663 let module_instance = closed_world_admission_module_instance(
2664 closed_world_bundle,
2665 module_qualified_symbols,
2666 )
2667 .cloned();
2668 closed_world_admission_summary.record_refusal(
2669 *pass_id,
2670 module_instance,
2671 closed_world_admission_reasons.clone(),
2672 );
2673 TransformPassDispatchResultV0::blocked(
2674 pass_id,
2675 input_byte_len,
2676 TransformBlockedReasonV0::ClosedWorldAdmission {
2677 reasons: closed_world_admission_reasons,
2678 },
2679 "closed-world admission evidence was incomplete before destructive dispatch",
2680 )
2681 } else if let Some(reason) = flatten_precondition_failure {
2682 TransformPassDispatchResultV0::blocked(
2683 pass_id,
2684 input_byte_len,
2685 reason,
2686 "fresh discharge ledger evidence required for flatten rewrite",
2687 )
2688 } else if let Some(reason) = closed_world_precondition_failure {
2689 TransformPassDispatchResultV0::blocked(
2690 pass_id,
2691 input_byte_len,
2692 reason,
2693 "closed-world bundle required before mutation",
2694 )
2695 } else {
2696 match runtime_entry.as_ref().map(|entry| entry.implementation) {
2697 Some(TransformRuntimePassImplementationV0::TextLocal(handler)) => {
2698 dispatch_text_local_pass(
2699 pass_id,
2700 handler,
2701 &document.current_ir,
2702 dialect,
2703 context,
2704 )
2705 }
2706 Some(TransformRuntimePassImplementationV0::ModuleEvaluation(pass)) => {
2707 let pass_input_css = textual_bridge.materialize_current_css(&document);
2708 dispatch_module_evaluation_pass(pass_id, pass, pass_input_css, dialect, context)
2709 }
2710 Some(TransformRuntimePassImplementationV0::Structural(handler)) => {
2711 if should_maintain_document_lex_cache {
2712 textual_bridge.materialize_current_css(&document);
2713 }
2714 dispatch_structural_pass(
2715 pass_id,
2716 handler,
2717 document.current_ir_mut(),
2718 TransformStructuralDispatchContextV0 {
2719 dialect,
2720 execution: context,
2721 closed_world_bundle,
2722 module_qualified_symbols,
2723 module_reachable_class_names: module_reachable_class_names.as_deref(),
2724 reachability_precision_ceiling,
2725 },
2726 )
2727 }
2728 Some(TransformRuntimePassImplementationV0::Emission(pass)) => {
2729 dispatch_emission_pass(pass_id, pass, input_byte_len)
2730 }
2731 None => None,
2732 }
2733 .unwrap_or_else(|| {
2734 TransformPassDispatchResultV0::blocked(
2735 pass_id,
2736 input_byte_len,
2737 TransformBlockedReasonV0::PassImplementation,
2738 "unknown pass id in execution plan",
2739 )
2740 })
2741 };
2742
2743 let semantic_mutation_spans = dispatch_result.semantic_mutation_spans();
2744 let mut winner_equality_tier = None;
2745 let blocked_before_semantic_observation = matches!(
2746 dispatch_result.decision,
2747 TransformDecisionDraftV0::Blocked { .. }
2748 );
2749 if !strict_refused_before_dispatch
2750 && blocked_before_semantic_observation
2751 && pass.is_some_and(semantic_preservation_applies)
2752 {
2753 semantic_preservation_telemetry.record_blocked_before_observation();
2754 } else if !strict_refused_before_dispatch
2755 && let (Some(pass_kind), Some(input_ir)) =
2756 (pass, semantic_preservation_input_ir.as_ref())
2757 {
2758 let enforcement_context = TransformSemanticPreservationEnforcementContextV0 {
2759 closed_world_bundle,
2760 module_qualified_symbols,
2761 module_reachable_class_names: module_reachable_class_names.as_deref(),
2762 projection: &semantic_preservation_projection,
2763 mutation_spans: semantic_mutation_spans.as_slice(),
2764 cascade_environment: context.cascade_environment.as_ref(),
2765 observe_pending_textual_output: strict_pass_ids.contains(pass_kind.id()),
2766 };
2767 let (enforced, winner_equality) = enforce_semantic_preservation_for_dispatch_result(
2768 pass_kind,
2769 input_ir,
2770 &mut document,
2771 dispatch_result,
2772 &mut semantic_preservation_telemetry,
2773 enforcement_context,
2774 );
2775 dispatch_result = enforced;
2776 if let Some(evaluation) = winner_equality {
2777 let strict_rollback_reasons = if strict_pass_ids.contains(pass_kind.id()) {
2778 strict_rollback_reasons(pass_kind, &evaluation)
2779 } else {
2780 Vec::new()
2781 };
2782 winner_equality_tier = Some(evaluation.tier.clone());
2783 if semantic_trust_recording.records() {
2784 winner_equality_obligations.extend(evaluation.obligations.clone());
2785 }
2786 if !strict_rollback_reasons.is_empty() {
2787 dispatch_result = apply_strict_winner_rollback(
2788 pass_kind,
2789 input_ir,
2790 &mut document,
2791 strict_rollback_reasons,
2792 dispatch_result,
2793 &mut strict_policy_summary,
2794 );
2795 }
2796 }
2797 }
2798 let preserved_output_signature = transform_content_signature(document.current_css());
2799
2800 let TransformPassDispatchResultV0 {
2801 next_textual_css,
2802 document_ir_updated,
2803 decision: decision_draft,
2804 css_module_evaluation: dispatched_css_module_evaluation,
2805 css_import_inlines: dispatched_css_import_inlines,
2806 css_module_composes_exports: dispatched_css_module_composes_exports,
2807 design_token_routes: dispatched_design_token_routes,
2808 semantic_removals: dispatched_semantic_removals,
2809 provenance_mutation_spans,
2810 } = dispatch_result;
2811 let decision = decision_draft.finalize(
2812 input_content_signature,
2813 preserved_output_signature,
2814 discharge_evidence,
2815 semantic_trust_recording.decision_tier(pass, winner_equality_tier),
2816 );
2817 let outcome = decision.compatibility_outcome().clone();
2818 if let Some(evaluation) = dispatched_css_module_evaluation {
2819 css_module_evaluation = Some(evaluation);
2820 }
2821 if !dispatched_css_import_inlines.is_empty() {
2822 css_import_inlines = dispatched_css_import_inlines;
2823 }
2824 if !dispatched_css_module_composes_exports.is_empty() {
2825 css_module_composes_exports = dispatched_css_module_composes_exports;
2826 }
2827 if !dispatched_design_token_routes.is_empty() {
2828 design_token_routes = dispatched_design_token_routes;
2829 }
2830 semantic_removals.extend(dispatched_semantic_removals);
2831 if document_ir_updated {
2832 let output_byte_len = document.current_byte_len();
2833 let mut mutation_spans = match provenance_mutation_spans {
2834 Some(mutation_spans) => mutation_spans,
2835 None => {
2836 vec![TransformProvenanceMutationSpanV0 {
2837 source_span_start: 0,
2838 source_span_end: input_byte_len,
2839 generated_span_start: 0,
2840 generated_span_end: output_byte_len,
2841 node_key: None,
2842 }]
2843 }
2844 };
2845 stamp_mutation_span_node_keys(
2846 mutation_spans.as_mut_slice(),
2847 &coordinate_map,
2848 stable_ir_nodes.as_slice(),
2849 );
2850 if should_maintain_document_lex_cache {
2851 let next_css = document.current_css().to_string();
2852 let pass_input_css = textual_bridge.materialize_current_css(&document);
2853 super::lex_cache::update_cached_lex_from_splice(
2854 pass_input_css,
2855 &next_css,
2856 dialect,
2857 mutation_spans.as_slice(),
2858 );
2859 }
2860 coordinate_map.apply_mutation_spans(mutation_spans.as_slice());
2861 outcome_mutation_spans.push(mutation_spans);
2862 } else {
2863 match next_textual_css {
2864 Some(next_css) => {
2865 let mut mutation_spans = match provenance_mutation_spans {
2866 Some(mutation_spans) => mutation_spans,
2867 _ => {
2868 let pass_input_css = textual_bridge.materialize_current_css(&document);
2869 derive_transform_mutation_spans(pass_input_css, &next_css)
2870 }
2871 };
2872 stamp_mutation_span_node_keys(
2873 mutation_spans.as_mut_slice(),
2874 &coordinate_map,
2875 stable_ir_nodes.as_slice(),
2876 );
2877 if should_maintain_document_lex_cache {
2878 let pass_input_css = textual_bridge.materialize_current_css(&document);
2879 super::lex_cache::update_cached_lex_from_splice(
2880 pass_input_css,
2881 &next_css,
2882 dialect,
2883 mutation_spans.as_slice(),
2884 );
2885 }
2886 coordinate_map.apply_mutation_spans(mutation_spans.as_slice());
2887 outcome_mutation_spans.push(mutation_spans);
2888 document.replace_with_css(next_css);
2889 }
2890 None => {
2891 outcome_mutation_spans.push(Vec::new());
2892 }
2893 }
2894 }
2895 decisions.push(decision);
2896 outcomes.push(outcome);
2897 }
2898
2899 let executed_pass_ids = outcomes
2900 .iter()
2901 .filter(|outcome| outcome.status != TransformPassRuntimeStatus::PlannedOnly)
2902 .map(|outcome| outcome.pass_id)
2903 .collect::<Vec<_>>();
2904 let planned_only_pass_ids = outcomes
2905 .iter()
2906 .filter(|outcome| outcome.status == TransformPassRuntimeStatus::PlannedOnly)
2907 .map(|outcome| outcome.pass_id)
2908 .collect::<Vec<_>>();
2909 let mutation_count = outcomes
2910 .iter()
2911 .map(|outcome| outcome.mutation_count)
2912 .sum::<usize>();
2913 let provenance_preserved = outcomes.iter().all(|outcome| outcome.provenance_preserved);
2914 let provenance_derivation_forest =
2915 provenance_derivation_forest_from_outcomes(&outcomes, &outcome_mutation_spans);
2916 let cascade_proof_obligations = summarize_cascade_proof_obligations(cascade_proof_obligations);
2917 let discharge_ledger_telemetry =
2918 summarize_discharge_ledger_telemetry(&cascade_proof_obligations);
2919 let structural_ir_transaction_telemetry = structural_ir_transaction_telemetry_snapshot();
2920 let output_byte_len = document.current_byte_len();
2921 let output_css = document.output_css();
2922 let module_qualified_shake = module_qualified_symbols.map(|symbols| {
2923 let tree_shake_pass_ids = [
2924 TransformPassKind::TreeShakeClass.id(),
2925 TransformPassKind::TreeShakeKeyframes.id(),
2926 TransformPassKind::TreeShakeValue.id(),
2927 TransformPassKind::TreeShakeCustomProperty.id(),
2928 ];
2929 TransformModuleQualifiedShakeSummaryV0 {
2930 module_instance: symbols.module_instance().clone(),
2931 removed_count: semantic_removals
2932 .iter()
2933 .filter(|removal| tree_shake_pass_ids.contains(&removal.pass_id))
2934 .count(),
2935 }
2936 });
2937
2938 TransformExecutionSummaryV0 {
2939 schema_version: "0",
2940 product: "omena-transform-passes.execution",
2941 input_byte_len: source.len(),
2942 output_byte_len,
2943 requested_pass_ids,
2944 ordered_pass_ids,
2945 executed_pass_ids,
2946 planned_only_pass_ids,
2947 mutation_count,
2948 provenance_preserved,
2949 output_css,
2950 css_module_evaluation,
2951 css_import_inlines,
2952 css_module_composes_exports,
2953 design_token_routes,
2954 semantic_removals,
2955 module_qualified_shake,
2956 cascade_proof_obligations,
2957 winner_equality_obligations,
2958 provenance_derivation_forest,
2959 structural_ir_transaction_telemetry,
2960 semantic_preservation_telemetry,
2961 discharge_ledger_telemetry,
2962 strict_policy: strict_policy_summary,
2963 closed_world_admission: closed_world_admission_summary,
2964 decisions,
2965 outcomes,
2966 pass_plan,
2967 }
2968}
2969
2970fn closed_world_admission_module_instance<'a>(
2971 closed_world_bundle: Option<&'a ClosedWorldBundleV0>,
2972 module_qualified_symbols: Option<&'a ModuleQualifiedSymbolSetV0>,
2973) -> Option<&'a ModuleInstanceKeyV0> {
2974 module_qualified_symbols
2975 .map(ModuleQualifiedSymbolSetV0::module_instance)
2976 .or_else(|| {
2977 closed_world_bundle.and_then(|bundle| match bundle.linked_modules() {
2978 [module_instance] => Some(module_instance),
2979 _ => None,
2980 })
2981 })
2982}
2983
2984fn closed_world_admission_o1_reasons(
2985 census: Option<&CssModuleTokenOwnershipCensusV0>,
2986 module_instance: Option<&ModuleInstanceKeyV0>,
2987 pass_kind: TransformPassKind,
2988) -> Vec<TransformStrictPolicyReasonV0> {
2989 if pass_kind != TransformPassKind::TreeShakeClass {
2990 return Vec::new();
2991 }
2992 let Some(census) = census else {
2993 return Vec::new();
2997 };
2998
2999 let collision_reasons = census
3000 .module_token_collisions
3001 .iter()
3002 .filter(|collision| {
3003 module_instance
3004 .is_none_or(|module_instance| collision.module_instances.contains(module_instance))
3005 })
3006 .map(
3007 |collision| TransformStrictPolicyReasonV0::OwnershipNotSeparable {
3008 token: collision.emitted_token.clone(),
3009 module_paths: collision.module_paths.clone(),
3010 },
3011 );
3012 let unattributed_reasons = census.unattributed_emitted_tokens.iter().map(|token| {
3013 TransformStrictPolicyReasonV0::OwnershipNotSeparable {
3014 token: token.clone(),
3015 module_paths: Vec::new(),
3016 }
3017 });
3018 let interface_mismatch_reasons = census
3019 .interface_mismatches
3020 .iter()
3021 .filter(|mismatch| {
3022 module_instance
3023 .is_none_or(|module_instance| &mismatch.module_instance == module_instance)
3024 })
3025 .map(
3026 |mismatch| TransformStrictPolicyReasonV0::ClosedWorldEvidenceIncomplete {
3027 missing: vec![format!(
3028 "cssModuleInterfaceToken:{}",
3029 mismatch.promised_token
3030 )],
3031 },
3032 );
3033 let mut reasons = collision_reasons
3034 .chain(unattributed_reasons)
3035 .chain(interface_mismatch_reasons)
3036 .collect::<Vec<_>>();
3037 if !census.complete && reasons.is_empty() {
3038 reasons.push(
3039 TransformStrictPolicyReasonV0::ClosedWorldEvidenceIncomplete {
3040 missing: if census.unavailable_reasons.is_empty() {
3041 vec!["cssModuleTokenOwnershipCensus".to_string()]
3042 } else {
3043 census.unavailable_reasons.clone()
3044 },
3045 },
3046 );
3047 }
3048 if reasons.is_empty()
3049 && checked_token_ownership_admission_v0(census, module_instance, pass_kind).is_none()
3050 {
3051 reasons.push(
3052 TransformStrictPolicyReasonV0::ClosedWorldEvidenceIncomplete {
3053 missing: vec![format!("proofKernelToken:{}", pass_kind.id())],
3054 },
3055 );
3056 }
3057 reasons
3058}
3059
3060fn checked_token_ownership_admission_v0(
3061 census: &CssModuleTokenOwnershipCensusV0,
3062 module_instance: Option<&ModuleInstanceKeyV0>,
3063 pass_kind: TransformPassKind,
3064) -> Option<omena_cascade_proof::RewriteIssuanceTokenV0> {
3065 let module_label = module_instance.map_or_else(
3066 || "workspace".to_owned(),
3067 |module_instance| {
3068 format!(
3069 "{}#{}",
3070 module_instance.module().as_str(),
3071 module_instance.configuration().as_str()
3072 )
3073 },
3074 );
3075 let before = RewriteTermV0::apply(
3076 "closedWorldAdmissionRequested",
3077 vec![
3078 RewriteTermV0::atom(pass_kind.id()),
3079 RewriteTermV0::atom(module_label.clone()),
3080 ],
3081 );
3082 let after = RewriteTermV0::apply(
3083 "closedWorldAdmissionGranted",
3084 vec![
3085 RewriteTermV0::atom(pass_kind.id()),
3086 RewriteTermV0::atom(module_label.clone()),
3087 ],
3088 );
3089 let catalog = RewriteRuleCatalogV0 {
3090 schema_version: REWRITE_RULE_CATALOG_SCHEMA_VERSION_V0.to_owned(),
3091 operators: vec![
3092 RewriteOperatorV0 {
3093 operator: "closedWorldAdmissionRequested".to_owned(),
3094 arity: 2,
3095 },
3096 RewriteOperatorV0 {
3097 operator: "closedWorldAdmissionGranted".to_owned(),
3098 arity: 2,
3099 },
3100 ],
3101 rules: vec![RewriteRuleV0 {
3102 rule_id: "closed-world-token-ownership-admission-v0".to_owned(),
3103 before_pattern: RewritePatternV0::apply(
3104 "closedWorldAdmissionRequested",
3105 vec![
3106 RewritePatternV0::variable("pass"),
3107 RewritePatternV0::variable("module"),
3108 ],
3109 ),
3110 after_pattern: RewritePatternV0::apply(
3111 "closedWorldAdmissionGranted",
3112 vec![
3113 RewritePatternV0::variable("pass"),
3114 RewritePatternV0::variable("module"),
3115 ],
3116 ),
3117 side_condition_kind: RewriteSideConditionKindV0::TokenOwnershipSeparability,
3118 }],
3119 };
3120 let mut modeled_preimage_count = 0;
3121 let ownerships = census
3122 .token_ownerships
3123 .iter()
3124 .filter_map(|ownership| {
3125 let module_paths = if let Some(module_instance) = module_instance {
3126 ownership
3127 .module_instances
3128 .iter()
3129 .zip(&ownership.module_paths)
3130 .filter_map(|(candidate, path)| {
3131 (candidate == module_instance).then_some(path.clone())
3132 })
3133 .collect::<Vec<_>>()
3134 } else {
3135 ownership.module_paths.clone()
3136 };
3137 if module_instance.is_some() && module_paths.is_empty() {
3138 return None;
3139 }
3140 modeled_preimage_count += ownership.original_names.len();
3141 Some(TokenOwnershipCertEntryV0 {
3142 emitted_token: ownership.emitted_token.clone(),
3143 module_paths,
3144 })
3145 })
3146 .collect::<Vec<_>>();
3147 let certificate = RewriteCertificateEnvelopeV0 {
3148 schema_version: REWRITE_CERTIFICATE_SCHEMA_VERSION_V0.to_owned(),
3149 max_depth: 1,
3150 max_nodes: 1,
3151 certificate: RewriteCertificateV0::Rewrite {
3152 rule_id: "closed-world-token-ownership-admission-v0".to_owned(),
3153 substitution: vec![
3154 RewriteSubstitutionEntryV0 {
3155 variable: "module".to_owned(),
3156 term: RewriteTermV0::atom(module_label),
3157 },
3158 RewriteSubstitutionEntryV0 {
3159 variable: "pass".to_owned(),
3160 term: RewriteTermV0::atom(pass_kind.id()),
3161 },
3162 ],
3163 side_condition: SideConditionCertV0::TokenOwnershipSeparability {
3164 certificate: TokenOwnershipSeparabilityCertV0 {
3165 complete: census.complete && census.unavailable_reasons.is_empty(),
3166 modeled_preimage_count,
3167 emitted_token_count: ownerships.len()
3168 + census.unattributed_emitted_tokens.len(),
3169 ownerships,
3170 unattributed_emitted_token_count: census.unattributed_emitted_tokens.len(),
3171 interface_mismatch_count: census
3172 .interface_mismatches
3173 .iter()
3174 .filter(|mismatch| {
3175 module_instance.is_none_or(|module_instance| {
3176 &mismatch.module_instance == module_instance
3177 })
3178 })
3179 .count(),
3180 },
3181 },
3182 },
3183 };
3184 check_rewrite_certificate_v0(
3185 &before,
3186 &after,
3187 &catalog,
3188 &certificate,
3189 &CanonicalRewriteAssumptionsV0::default(),
3190 )
3191 .ok()
3192 .filter(|token| {
3193 token.matches_endpoints_v0(&before, &after) && token.matches_catalog_v0(&catalog)
3194 })
3195}
3196
3197struct ClosedWorldAdmissionO3V0<'a> {
3198 reasons: Vec<TransformStrictPolicyReasonV0>,
3199 module_instance: Option<&'a ModuleInstanceKeyV0>,
3200}
3201
3202fn closed_world_admission_o3_reasons<'a>(
3203 bundle: &'a ClosedWorldBundleV0,
3204 module_qualified_symbols: Option<&'a ModuleQualifiedSymbolSetV0>,
3205) -> ClosedWorldAdmissionO3V0<'a> {
3206 let module_instance =
3217 closed_world_admission_module_instance(Some(bundle), module_qualified_symbols);
3218 if module_instance.is_some_and(|module_instance| {
3219 !bundle
3220 .linked_modules()
3221 .iter()
3222 .any(|linked| linked == module_instance)
3223 }) {
3224 return ClosedWorldAdmissionO3V0 {
3225 reasons: Vec::new(),
3226 module_instance,
3227 };
3228 }
3229 let mut missing = Vec::new();
3230 let checked_modules = module_instance.map_or_else(
3231 || bundle.linked_modules(),
3232 |module_instance| std::slice::from_ref(module_instance),
3233 );
3234 if checked_modules.is_empty() {
3235 missing.push("moduleInstance".to_string());
3236 }
3237 if checked_modules.iter().any(|module_instance| {
3238 !bundle.interface_hashes().entries().iter().any(|entry| {
3239 &entry.module_instance == module_instance
3240 && matches!(
3241 entry.availability,
3242 ClosedWorldInterfaceHashAvailabilityV0::Known { .. }
3243 )
3244 })
3245 }) {
3246 missing.push("interfaceHash".to_string());
3247 }
3248 if checked_modules.iter().any(|module_instance| {
3249 bundle.module_reachability_evidence(module_instance)
3250 == ClosedWorldModuleReachabilityEvidenceV0::ModuleReachabilityInputAbsent
3251 }) {
3252 missing.push("moduleReachabilityInputAbsent".to_string());
3253 }
3254 if bundle
3255 .source_precision()
3256 .is_none_or(|precision| precision.unknown_source_count > 0)
3257 {
3258 missing.push("sourcePrecision".to_string());
3259 }
3260 let reasons = if missing.is_empty() {
3261 Vec::new()
3262 } else {
3263 vec![TransformStrictPolicyReasonV0::ClosedWorldEvidenceIncomplete { missing }]
3264 };
3265 ClosedWorldAdmissionO3V0 {
3266 reasons,
3267 module_instance,
3268 }
3269}
3270
3271fn closed_world_admission_o2_reasons(
3272 bundle: &ClosedWorldBundleV0,
3273 module_qualified_symbols: Option<&ModuleQualifiedSymbolSetV0>,
3274 module_reachable_class_names: Option<&[String]>,
3275 pass_kind: TransformPassKind,
3276) -> Vec<TransformStrictPolicyReasonV0> {
3277 if pass_kind != TransformPassKind::TreeShakeClass {
3278 return Vec::new();
3279 }
3280 let module_instance =
3281 closed_world_admission_module_instance(Some(bundle), module_qualified_symbols);
3282 let target_modules = module_instance.map_or_else(
3283 || bundle.linked_modules(),
3284 |module_instance| std::slice::from_ref(module_instance),
3285 );
3286 let checked = module_reachable_class_names
3287 .unwrap_or_else(|| bundle.reachability().class_names())
3288 .iter()
3289 .map(String::as_str)
3290 .collect::<BTreeSet<_>>();
3291 let inbound = bundle
3292 .composes_edges()
3293 .iter()
3294 .filter(|edge| target_modules.contains(&edge.to_module))
3295 .collect::<Vec<_>>();
3296 if inbound.iter().any(|edge| {
3297 bundle
3298 .composes_origin_symbol_is_reachable(&edge.from_module, edge.from_symbol.as_str())
3299 .is_none()
3300 }) {
3301 return vec![TransformStrictPolicyReasonV0::EvidenceUnavailable];
3302 }
3303 inbound
3304 .into_iter()
3305 .filter(|edge| {
3306 bundle.composes_origin_symbol_is_reachable(&edge.from_module, edge.from_symbol.as_str())
3307 == Some(true)
3308 })
3309 .filter(|edge| !checked.contains(edge.to_symbol.as_str()))
3310 .map(|edge| TransformStrictPolicyReasonV0::LivenessNotClosed {
3311 symbol: edge.to_symbol.clone(),
3312 from_module: edge.from_module.clone(),
3313 via_edge: "composes",
3314 })
3315 .collect()
3316}
3317
3318fn summarize_discharge_ledger_telemetry(
3319 report: &crate::TransformCascadeProofObligationReportV0,
3320) -> TransformDischargeLedgerTelemetryV0 {
3321 let lookups = report
3322 .obligations
3323 .iter()
3324 .filter_map(|obligation| obligation.discharge_ledger_lookup.as_ref())
3325 .collect::<Vec<_>>();
3326 let lookup_count = lookups.len() as u64;
3327 let matched_lookup_count = lookups
3328 .iter()
3329 .filter(|lookup| lookup.status == DischargeLedgerLookupStatusV0::Matched)
3330 .count() as u64;
3331 let accepted_stamp_count = lookups
3332 .iter()
3333 .filter(|lookup| lookup.can_apply_family_stamp())
3334 .count() as u64;
3335
3336 TransformDischargeLedgerTelemetryV0 {
3337 lookup_count,
3338 matched_lookup_count,
3339 accepted_stamp_count,
3340 blocked_lookup_count: lookup_count.saturating_sub(accepted_stamp_count),
3341 }
3342}
3343
3344fn strict_plan_refusal_reasons(
3345 pass: TransformPassKind,
3346 context: &TransformExecutionContextV0,
3347) -> Vec<TransformStrictPolicyReasonV0> {
3348 let driven_axes = driven_transform_axes().into_iter().collect::<BTreeSet<_>>();
3349 let required_axes = strict_required_winner_axes(pass);
3350 let mut reasons = required_axes
3351 .iter()
3352 .filter(|axis| !driven_axes.contains(axis))
3353 .copied()
3354 .map(|axis| TransformStrictPolicyReasonV0::RequiredAxisUnavailable { axis })
3355 .collect::<Vec<_>>();
3356 if required_axes.contains(&TransformWinnerEqualityAxisV0::CascadeLevel)
3357 && context.cascade_environment.is_none()
3358 {
3359 reasons.push(TransformStrictPolicyReasonV0::CascadeEnvironmentUnavailable);
3360 }
3361 reasons
3362}
3363
3364fn strict_rollback_reasons(
3365 pass: TransformPassKind,
3366 evaluation: &TransformWinnerEqualityEvaluationV0,
3367) -> Vec<TransformStrictPolicyReasonV0> {
3368 let required_axes = strict_required_winner_axes(pass)
3369 .into_iter()
3370 .collect::<BTreeSet<_>>();
3371 let mut changed_axes = BTreeSet::new();
3372 let mut absences = Vec::new();
3373 for obligation in &evaluation.obligations {
3374 match &obligation.observation {
3375 TransformWinnerEqualityObservationV0::ObservedEqual { .. }
3376 | TransformWinnerEqualityObservationV0::ObservedGuardedEqual { .. } => {}
3377 TransformWinnerEqualityObservationV0::ObservedDifferent { axes, .. } => {
3378 changed_axes.extend(axes.iter().copied());
3379 }
3380 TransformWinnerEqualityObservationV0::Absent { reasons } => {
3381 for reason in reasons {
3382 if required_axes.contains(&reason.axis) && !absences.contains(reason) {
3383 absences.push(reason.clone());
3384 }
3385 }
3386 }
3387 }
3388 }
3389 for reason in &evaluation.unresolved_reasons {
3390 if required_axes.contains(&reason.axis) && !absences.contains(reason) {
3391 absences.push(reason.clone());
3392 }
3393 }
3394
3395 let mut reasons = Vec::new();
3396 if !changed_axes.is_empty() {
3397 reasons.push(TransformStrictPolicyReasonV0::WinnerChanged {
3398 axes: changed_axes.into_iter().collect(),
3399 });
3400 }
3401 if !absences.is_empty() {
3402 reasons.push(TransformStrictPolicyReasonV0::ObservationUnavailable { reasons: absences });
3403 }
3404 reasons
3405}
3406
3407fn apply_strict_winner_rollback(
3408 pass: TransformPassKind,
3409 input_ir: &TransformIrV0,
3410 document: &mut TransformExecutionDocumentV0,
3411 reasons: Vec<TransformStrictPolicyReasonV0>,
3412 dispatch_result: TransformPassDispatchResultV0,
3413 summary: &mut TransformStrictPolicySummaryV0,
3414) -> TransformPassDispatchResultV0 {
3415 if reasons.is_empty() {
3416 return dispatch_result;
3417 }
3418
3419 document.current_ir = input_ir.clone();
3420 summary.record_rollback(pass.id(), reasons.clone());
3421 TransformPassDispatchResultV0::rejected(
3422 pass.id(),
3423 input_ir.source_text().len(),
3424 TransformRejectionReasonV0::StrictVerification { reasons },
3425 "strict verification rolled back a winner-sensitive rewrite",
3426 )
3427}
3428
3429#[derive(Clone, Copy)]
3430struct TransformSemanticPreservationEnforcementContextV0<'a> {
3431 closed_world_bundle: Option<&'a ClosedWorldBundleV0>,
3432 module_qualified_symbols: Option<&'a ModuleQualifiedSymbolSetV0>,
3433 module_reachable_class_names: Option<&'a [String]>,
3434 projection: &'a SemanticObservationProjectionV0,
3435 mutation_spans: &'a [TransformProvenanceMutationSpanV0],
3436 cascade_environment: Option<&'a crate::model::TransformCascadeEnvironmentV0>,
3437 observe_pending_textual_output: bool,
3438}
3439
3440fn enforce_semantic_preservation_for_dispatch_result(
3441 pass: TransformPassKind,
3442 input_ir: &TransformIrV0,
3443 document: &mut TransformExecutionDocumentV0,
3444 dispatch_result: TransformPassDispatchResultV0,
3445 telemetry: &mut TransformSemanticPreservationTelemetryV0,
3446 context: TransformSemanticPreservationEnforcementContextV0<'_>,
3447) -> (
3448 TransformPassDispatchResultV0,
3449 Option<TransformWinnerEqualityEvaluationV0>,
3450) {
3451 let input_scope = SemanticObservationScopeV0::for_pass(
3452 pass,
3453 document.dialect,
3454 context.closed_world_bundle,
3455 context.module_qualified_symbols,
3456 context.module_reachable_class_names,
3457 context.projection,
3458 );
3459 let output_scope = input_scope.without_ignored_source_ranges();
3460 let pass_id = pass.id();
3461 let output_ir;
3462 let observed_output_ir = if context.observe_pending_textual_output
3463 && let Some(next_css) = dispatch_result.next_textual_css.as_deref()
3464 {
3465 output_ir = lower_transform_ir_from_source(
3466 next_css,
3467 document.dialect,
3468 "omena-transform-passes.winner-equality.output",
3469 );
3470 &output_ir
3471 } else if semantic_preservation_needs_fresh_output_ir(pass) {
3472 output_ir = lower_transform_ir_from_source(
3473 document.current_ir.source_text(),
3474 document.dialect,
3475 "omena-transform-passes.semantic-preservation.output",
3476 );
3477 &output_ir
3478 } else {
3479 &document.current_ir
3480 };
3481 let decision = compare_semantic_observation_for_pass_with_scopes(
3482 pass_id,
3483 input_ir,
3484 observed_output_ir,
3485 input_scope,
3486 output_scope,
3487 );
3488 telemetry.record(&decision);
3489 if decision.preserved {
3490 let winner_equality = (!context.mutation_spans.is_empty()).then(|| {
3491 evaluate_transform_winner_equality(
3492 pass,
3493 input_ir,
3494 observed_output_ir,
3495 context.mutation_spans,
3496 document.dialect,
3497 TransformWinnerEqualityContextV0 {
3498 input_scope,
3499 output_scope,
3500 cascade_environment: context.cascade_environment,
3501 },
3502 )
3503 });
3504 return (dispatch_result, winner_equality);
3505 }
3506
3507 document.current_ir = input_ir.clone();
3508 (
3509 TransformPassDispatchResultV0::rejected(
3510 pass_id,
3511 input_ir.source_text().len(),
3512 TransformRejectionReasonV0::SemanticPreservation,
3513 "semantic preservation check refused a structural rewrite",
3514 ),
3515 None,
3516 )
3517}
3518
3519fn semantic_preservation_needs_fresh_output_ir(pass: TransformPassKind) -> bool {
3520 matches!(
3521 pass,
3522 TransformPassKind::NestingUnwrap
3523 | TransformPassKind::ScopeFlatten
3524 | TransformPassKind::LayerFlatten
3525 )
3526}
3527
3528fn flatten_discharge_precondition_failure(
3529 pass: Option<TransformPassKind>,
3530 obligations: &[TransformCascadeProofObligationV0],
3531) -> Option<TransformBlockedReasonV0> {
3532 if !matches!(
3533 pass,
3534 Some(TransformPassKind::ScopeFlatten | TransformPassKind::LayerFlatten)
3535 ) {
3536 return None;
3537 }
3538 if obligations.is_empty() {
3539 return Some(TransformBlockedReasonV0::DischargeMissing {
3540 lookup_status: None,
3541 verdict: None,
3542 });
3543 }
3544 let failed_obligation = obligations
3545 .iter()
3546 .find(|obligation| !obligation.accepted || !has_matching_discharge_evidence(obligation));
3547 if let Some(obligation) = failed_obligation {
3548 return Some(TransformBlockedReasonV0::DischargeMissing {
3549 lookup_status: obligation
3550 .discharge_ledger_lookup
3551 .as_ref()
3552 .map(|lookup| lookup.status),
3553 verdict: obligation
3554 .discharge_ledger_lookup
3555 .as_ref()
3556 .and_then(|lookup| lookup.verdict),
3557 });
3558 }
3559
3560 None
3561}
3562
3563fn has_matching_discharge_evidence(obligation: &TransformCascadeProofObligationV0) -> bool {
3564 let Some(lookup) = obligation.discharge_ledger_lookup.as_ref() else {
3565 return false;
3566 };
3567 let Some(evidence) = obligation.discharge_evidence.as_ref() else {
3568 return false;
3569 };
3570 lookup.can_apply_family_stamp()
3571 && evidence.guarantee_family == GuaranteeFamilyV0::LedgerBackedObligationDischarge
3572 && evidence.ledger_cell_key == lookup.cell_key
3573 && lookup.boundedness_kind.as_deref() == Some(evidence.boundedness_kind.as_str())
3574}
3575
3576fn flatten_discharge_evidence(
3577 pass: Option<TransformPassKind>,
3578 obligations: &[TransformCascadeProofObligationV0],
3579) -> Vec<TransformDischargeEvidenceV0> {
3580 if !matches!(
3581 pass,
3582 Some(TransformPassKind::ScopeFlatten | TransformPassKind::LayerFlatten)
3583 ) {
3584 return Vec::new();
3585 }
3586 obligations
3587 .iter()
3588 .filter_map(|obligation| obligation.discharge_evidence.clone())
3589 .collect()
3590}
3591
3592fn transform_pass_may_consume_lex_cache(pass: TransformPassKind) -> bool {
3593 omena_transform_cst::transform_pass_class(pass) == TransformPassClassV0::TextLocal
3594}
3595
3596fn next_document_lex_cache_consumer(
3597 ordered_pass_ids: &[&'static str],
3598 pass_index: usize,
3599) -> Option<TransformTextLocalExecutionModeV0> {
3600 ordered_pass_ids
3601 .iter()
3602 .skip(pass_index + 1)
3603 .filter_map(|pass_id| transform_pass_kind_from_id(pass_id))
3604 .filter(|kind| transform_pass_may_consume_lex_cache(*kind))
3605 .find_map(text_local_execution_mode_for_kind)
3606}
3607
3608fn compose_ir_transaction_mutation_span_batches(
3609 input_byte_len: usize,
3610 output_byte_len: usize,
3611 span_batches: &[Vec<TransformProvenanceMutationSpanV0>],
3612) -> Option<Vec<TransformProvenanceMutationSpanV0>> {
3613 if span_batches.is_empty() {
3614 return None;
3615 }
3616
3617 let mut coordinate_map = TransformSpanCoordinateMapV0::new(input_byte_len);
3618 let mut composed_spans = Vec::new();
3619 for batch in span_batches {
3620 if batch.is_empty() {
3621 return None;
3622 }
3623 remap_composed_generated_spans(composed_spans.as_mut_slice(), batch.as_slice())?;
3624 for span in batch {
3625 let (source_span_start, source_span_end) = coordinate_map
3626 .map_current_span_to_original(span.source_span_start, span.source_span_end)?;
3627 composed_spans.push(TransformProvenanceMutationSpanV0 {
3628 source_span_start,
3629 source_span_end,
3630 generated_span_start: span.generated_span_start,
3631 generated_span_end: span.generated_span_end,
3632 node_key: None,
3633 });
3634 }
3635 coordinate_map.apply_mutation_spans(batch.as_slice());
3636 }
3637
3638 if composed_spans
3639 .iter()
3640 .all(|span| span.generated_span_end <= output_byte_len)
3641 {
3642 Some(composed_spans)
3643 } else {
3644 None
3645 }
3646}
3647
3648fn remap_composed_generated_spans(
3649 composed_spans: &mut [TransformProvenanceMutationSpanV0],
3650 next_batch: &[TransformProvenanceMutationSpanV0],
3651) -> Option<()> {
3652 for span in composed_spans {
3653 span.generated_span_start =
3654 map_current_position_through_mutations(span.generated_span_start, next_batch)?;
3655 span.generated_span_end =
3656 map_current_position_through_mutations(span.generated_span_end, next_batch)?;
3657 if span.generated_span_start > span.generated_span_end {
3658 return None;
3659 }
3660 }
3661 Some(())
3662}
3663
3664struct TransformModuleEvaluationMaterializedOutput {
3665 css: String,
3666 detail: &'static str,
3667}
3668
3669fn materialize_transform_module_evaluation_output(
3670 input_css: &str,
3671 evaluation: &TransformModuleEvaluationV0,
3672 native_detail: &'static str,
3673 preserve_detail: &'static str,
3674) -> TransformModuleEvaluationMaterializedOutput {
3675 if !evaluation.may_consume_native_product_output() {
3676 return TransformModuleEvaluationMaterializedOutput {
3677 css: input_css.to_string(),
3678 detail: preserve_detail,
3679 };
3680 }
3681
3682 if let Some(native_edit_output) = evaluation.native_edit_output.as_ref() {
3683 if evaluation.native_output_matches_retained_oracle(native_edit_output) {
3684 return TransformModuleEvaluationMaterializedOutput {
3685 css: native_edit_output.clone(),
3686 detail: native_detail,
3687 };
3688 }
3689 return TransformModuleEvaluationMaterializedOutput {
3690 css: input_css.to_string(),
3691 detail: preserve_detail,
3692 };
3693 }
3694
3695 if let Some(native_css) =
3696 apply_transform_module_evaluation_native_edits(input_css, &evaluation.native_edits)
3697 && native_css == evaluation.evaluated_css
3698 && evaluation.native_output_matches_retained_oracle(native_css.as_str())
3699 {
3700 return TransformModuleEvaluationMaterializedOutput {
3701 css: native_css,
3702 detail: native_detail,
3703 };
3704 }
3705
3706 TransformModuleEvaluationMaterializedOutput {
3707 css: input_css.to_string(),
3708 detail: preserve_detail,
3709 }
3710}
3711
3712fn apply_transform_module_evaluation_native_edits(
3713 input_css: &str,
3714 native_edits: &[TransformModuleEvaluationNativeEditV0],
3715) -> Option<String> {
3716 if native_edits.is_empty() {
3717 return None;
3718 }
3719
3720 let mut edits = native_edits.to_vec();
3721 edits.sort_by_key(|edit| edit.start);
3722
3723 let mut previous_end = 0usize;
3724 for edit in &edits {
3725 if edit.start < previous_end
3726 || edit.start > edit.end
3727 || edit.end > input_css.len()
3728 || !input_css.is_char_boundary(edit.start)
3729 || !input_css.is_char_boundary(edit.end)
3730 {
3731 return None;
3732 }
3733 previous_end = edit.end;
3734 }
3735
3736 let mut output = input_css.to_string();
3737 for edit in edits.iter().rev() {
3738 output.replace_range(edit.start..edit.end, edit.replacement.as_str());
3739 }
3740 Some(output)
3741}
3742
3743#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3744struct TransformSpanMapSegmentV0 {
3745 current_start: usize,
3746 current_end: usize,
3747 original_start: usize,
3748}
3749
3750#[derive(Debug, Clone, PartialEq, Eq)]
3751struct TransformSpanCoordinateMapV0 {
3752 segments: Vec<TransformSpanMapSegmentV0>,
3753}
3754
3755impl TransformSpanCoordinateMapV0 {
3756 fn new(source_len: usize) -> Self {
3757 Self {
3758 segments: vec![TransformSpanMapSegmentV0 {
3759 current_start: 0,
3760 current_end: source_len,
3761 original_start: 0,
3762 }],
3763 }
3764 }
3765
3766 fn map_current_span_to_original(
3767 &self,
3768 current_start: usize,
3769 current_end: usize,
3770 ) -> Option<(usize, usize)> {
3771 let segment = self.segments.iter().find(|segment| {
3772 segment.current_start <= current_start && current_end <= segment.current_end
3773 })?;
3774 let original_start = segment.original_start + current_start - segment.current_start;
3775 let original_end = original_start + current_end.saturating_sub(current_start);
3776 Some((original_start, original_end))
3777 }
3778
3779 fn apply_mutation_spans(&mut self, mutation_spans: &[TransformProvenanceMutationSpanV0]) {
3780 if mutation_spans.is_empty() {
3781 return;
3782 }
3783
3784 let mut sorted_spans = mutation_spans.to_vec();
3785 sorted_spans.sort_by(|left, right| {
3786 left.source_span_start
3787 .cmp(&right.source_span_start)
3788 .then_with(|| left.source_span_end.cmp(&right.source_span_end))
3789 });
3790
3791 let mut next_segments = Vec::new();
3792 for segment in &self.segments {
3793 let mut cursor = segment.current_start;
3794 for span in &sorted_spans {
3795 if span.source_span_end <= cursor {
3796 continue;
3797 }
3798 if span.source_span_start >= segment.current_end {
3799 break;
3800 }
3801 let unchanged_end = span.source_span_start.min(segment.current_end);
3802 self.push_mapped_piece(
3803 segment,
3804 cursor,
3805 unchanged_end,
3806 &sorted_spans,
3807 &mut next_segments,
3808 );
3809 cursor = cursor.max(span.source_span_end.min(segment.current_end));
3810 }
3811 self.push_mapped_piece(
3812 segment,
3813 cursor,
3814 segment.current_end,
3815 &sorted_spans,
3816 &mut next_segments,
3817 );
3818 }
3819 self.segments = next_segments;
3820 }
3821
3822 fn push_mapped_piece(
3823 &self,
3824 segment: &TransformSpanMapSegmentV0,
3825 current_start: usize,
3826 current_end: usize,
3827 mutation_spans: &[TransformProvenanceMutationSpanV0],
3828 next_segments: &mut Vec<TransformSpanMapSegmentV0>,
3829 ) {
3830 if current_start >= current_end {
3831 return;
3832 }
3833 let Some(next_start) =
3834 map_current_position_through_mutations(current_start, mutation_spans)
3835 else {
3836 return;
3837 };
3838 let Some(next_end) = map_current_position_through_mutations(current_end, mutation_spans)
3839 else {
3840 return;
3841 };
3842 if next_start >= next_end {
3843 return;
3844 }
3845 next_segments.push(TransformSpanMapSegmentV0 {
3846 current_start: next_start,
3847 current_end: next_end,
3848 original_start: segment.original_start + current_start - segment.current_start,
3849 });
3850 }
3851}
3852
3853fn map_current_position_through_mutations(
3854 position: usize,
3855 mutation_spans: &[TransformProvenanceMutationSpanV0],
3856) -> Option<usize> {
3857 let mut delta = 0isize;
3858 for span in mutation_spans {
3859 if position < span.source_span_start {
3860 return apply_position_delta(position, delta);
3861 }
3862 if position <= span.source_span_end {
3863 return (position == span.source_span_start)
3864 .then(|| apply_position_delta(span.generated_span_start, 0))
3865 .flatten()
3866 .or_else(|| {
3867 (position == span.source_span_end)
3868 .then(|| apply_position_delta(span.generated_span_end, 0))
3869 .flatten()
3870 });
3871 }
3872 delta = span.generated_span_end as isize - span.source_span_end as isize;
3873 }
3874 apply_position_delta(position, delta)
3875}
3876
3877fn apply_position_delta(position: usize, delta: isize) -> Option<usize> {
3878 if delta >= 0 {
3879 position.checked_add(delta as usize)
3880 } else {
3881 position.checked_sub((-delta) as usize)
3882 }
3883}
3884
3885fn stamp_mutation_span_node_keys(
3892 mutation_spans: &mut [TransformProvenanceMutationSpanV0],
3893 coordinate_map: &TransformSpanCoordinateMapV0,
3894 stable_ir_nodes: &[StableTransformIrNodeV0],
3895) {
3896 for span in mutation_spans {
3897 let Some((original_start, original_end)) = coordinate_map
3898 .map_current_span_to_original(span.source_span_start, span.source_span_end)
3899 else {
3900 continue;
3901 };
3902 span.node_key =
3903 innermost_stable_node_key_for_span(original_start, original_end, stable_ir_nodes);
3904 }
3905}
3906
3907fn innermost_stable_node_key_for_span(
3908 original_start: usize,
3909 original_end: usize,
3910 stable_ir_nodes: &[StableTransformIrNodeV0],
3911) -> Option<omena_transform_cst::StableNodeKeyV0> {
3912 stable_ir_nodes
3913 .iter()
3914 .filter(|node| {
3915 let overlap_start = node.source_span_start.max(original_start);
3916 let overlap_end = node.source_span_end.min(original_end);
3917 overlap_start < overlap_end
3918 })
3919 .min_by_key(|node| {
3920 let contains =
3921 node.source_span_start <= original_start && original_end <= node.source_span_end;
3922 (
3923 usize::from(!contains),
3924 node.source_span_end.saturating_sub(node.source_span_start),
3925 )
3926 })
3927 .and_then(|node| node.additive_node_key().cloned())
3928}
3929
3930#[cfg(test)]
3931mod dispatch_table_tests {
3932 use super::*;
3933 use crate::model::TransformCascadeEnvironmentV0;
3934 use omena_cascade_proof::DischargeLedgerVerdictV0;
3935 use omena_evidence_graph::EvidenceNodeKeyV0;
3936 use omena_parser::{
3937 ClosedWorldLinkedModuleV0, ConfigurationHashV0, ModuleIdV0, ModuleInstanceKeyV0,
3938 };
3939 use omena_transform_cst::{
3940 IrEditRegionV0, IrTransactionV0, TransformPassClassV0, default_transform_pass_descriptors,
3941 };
3942
3943 fn mutation_span(
3944 source_span_start: usize,
3945 source_span_end: usize,
3946 generated_span_start: usize,
3947 generated_span_end: usize,
3948 ) -> TransformProvenanceMutationSpanV0 {
3949 TransformProvenanceMutationSpanV0 {
3950 source_span_start,
3951 source_span_end,
3952 generated_span_start,
3953 generated_span_end,
3954 node_key: None,
3955 }
3956 }
3957
3958 #[test]
3959 fn strict_winner_difference_restores_the_input_ir() {
3960 let input_source = ".a { color: red; }";
3961 let output_source = ".a { color: blue; }";
3962 let input_ir =
3963 lower_transform_ir_from_source(input_source, StyleDialect::Css, "strict-policy-input");
3964 let output_ir = lower_transform_ir_from_source(
3965 output_source,
3966 StyleDialect::Css,
3967 "strict-policy-output",
3968 );
3969 let environment = TransformCascadeEnvironmentV0::default();
3970 let evaluation = evaluate_transform_winner_equality(
3971 TransformPassKind::RuleMerging,
3972 &input_ir,
3973 &output_ir,
3974 &[mutation_span(0, input_source.len(), 0, output_source.len())],
3975 StyleDialect::Css,
3976 TransformWinnerEqualityContextV0 {
3977 input_scope: SemanticObservationScopeV0::default(),
3978 output_scope: SemanticObservationScopeV0::default(),
3979 cascade_environment: Some(&environment),
3980 },
3981 );
3982 let reasons = strict_rollback_reasons(TransformPassKind::RuleMerging, &evaluation);
3983 assert!(
3984 reasons.iter().any(|reason| matches!(
3985 reason,
3986 TransformStrictPolicyReasonV0::WinnerChanged { .. }
3987 ))
3988 );
3989
3990 let mut document = TransformExecutionDocumentV0::new(output_source, StyleDialect::Css);
3991 let dispatch_result = TransformPassDispatchResultV0::textual_mutation(
3992 TransformPassKind::RuleMerging.id(),
3993 input_source.len(),
3994 output_source.to_string(),
3995 1,
3996 "test mutation",
3997 );
3998 let mut summary = TransformStrictPolicySummaryV0::for_profile("strict-verification");
3999 let dispatch_result = apply_strict_winner_rollback(
4000 TransformPassKind::RuleMerging,
4001 &input_ir,
4002 &mut document,
4003 reasons,
4004 dispatch_result,
4005 &mut summary,
4006 );
4007
4008 assert_eq!(document.current_css(), input_source);
4009 assert_eq!(summary.rolled_back_count, 1);
4010 assert!(matches!(
4011 dispatch_result.decision,
4012 TransformDecisionDraftV0::Rejected {
4013 reason: TransformRejectionReasonV0::StrictVerification { .. },
4014 ..
4015 }
4016 ));
4017 }
4018
4019 #[test]
4020 fn reachability_precision_preserves_witness_bound_exactness() -> Result<(), String> {
4021 let context = TransformExecutionContextV0::default();
4022
4023 assert_eq!(
4024 classify_transform_reachability_precision(&context, true, Some(FactPrecision::Exact)),
4025 FactPrecision::Exact
4026 );
4027 assert_eq!(
4028 classify_transform_reachability_precision(
4029 &context,
4030 true,
4031 Some(FactPrecision::Heuristic),
4032 ),
4033 FactPrecision::Heuristic
4034 );
4035 assert_eq!(
4036 classify_transform_reachability_precision(&context, false, None),
4037 FactPrecision::Unknown
4038 );
4039 Ok(())
4040 }
4041
4042 #[test]
4043 fn text_local_dispatch_handlers_match_pass_descriptors() {
4044 let mut descriptor_pass_ids = default_transform_pass_descriptors()
4045 .into_iter()
4046 .filter(|descriptor| descriptor.pass_class == TransformPassClassV0::TextLocal)
4047 .map(|descriptor| descriptor.id)
4048 .collect::<Vec<_>>();
4049 let mut handler_pass_ids = text_local_pass_handlers()
4050 .iter()
4051 .map(|handler| handler.kind.id())
4052 .collect::<Vec<_>>();
4053
4054 descriptor_pass_ids.sort_unstable();
4055 handler_pass_ids.sort_unstable();
4056
4057 assert_eq!(handler_pass_ids.len(), 20);
4058 assert_eq!(handler_pass_ids, descriptor_pass_ids);
4059 }
4060
4061 #[test]
4062 fn discharge_decisions_block_stale_and_record_ledger_evidence() {
4063 let missing_lookup = omena_cascade_proof::DischargeLedgerLookupV0 {
4064 schema_version: "0",
4065 product: "omena-cascade-proof.discharge-ledger.lookup",
4066 cell_key: "missing-cell".to_string(),
4067 status: omena_cascade_proof::DischargeLedgerLookupStatusV0::Missing,
4068 obligation_family: None,
4069 cell_family: None,
4070 verdict: None,
4071 boundedness_kind: None,
4072 floor_reason: Some("ledger cell is absent"),
4073 };
4074 let accepted_without_stamp = TransformCascadeProofObligationV0 {
4075 pass_id: "scope-flatten",
4076 proof_product: "omena-cascade.scope-flatten-proof",
4077 accepted: true,
4078 blocked_reason: None,
4079 provenance_preserved: true,
4080 cascade_safe_witness: "scope flatten proof accepted".to_string(),
4081 source_span_start: Some(0),
4082 source_span_end: Some(1),
4083 checked_obligations: vec!["rootScopeOnly"],
4084 canonical_smt_input: None,
4085 discharge_ledger_lookup: Some(missing_lookup),
4086 discharge_evidence: None,
4087 proof_payload: serde_json::json!({ "accepted": true }),
4088 };
4089 assert_eq!(
4090 flatten_discharge_precondition_failure(
4091 Some(TransformPassKind::ScopeFlatten),
4092 std::slice::from_ref(&accepted_without_stamp),
4093 ),
4094 Some(TransformBlockedReasonV0::DischargeMissing {
4095 lookup_status: Some(DischargeLedgerLookupStatusV0::Missing),
4096 verdict: None,
4097 })
4098 );
4099
4100 let mut accepted_with_stale_lookup = accepted_without_stamp.clone();
4101 accepted_with_stale_lookup.discharge_ledger_lookup =
4102 Some(omena_cascade_proof::DischargeLedgerLookupV0 {
4103 schema_version: "0",
4104 product: "omena-cascade-proof.discharge-ledger.lookup",
4105 cell_key: "stale-cell".to_string(),
4106 status: DischargeLedgerLookupStatusV0::Stale,
4107 obligation_family: None,
4108 cell_family: None,
4109 verdict: None,
4110 boundedness_kind: None,
4111 floor_reason: Some("ledger pins do not match the runtime"),
4112 });
4113 let stale_reason = flatten_discharge_precondition_failure(
4114 Some(TransformPassKind::ScopeFlatten),
4115 std::slice::from_ref(&accepted_with_stale_lookup),
4116 );
4117 assert_eq!(
4118 stale_reason,
4119 Some(TransformBlockedReasonV0::DischargeMissing {
4120 lookup_status: Some(DischargeLedgerLookupStatusV0::Stale),
4121 verdict: None,
4122 })
4123 );
4124 let stale_wire = serde_json::to_value(stale_reason);
4125 assert!(stale_wire.is_ok());
4126 let Ok(stale_wire) = stale_wire else {
4127 return;
4128 };
4129 assert_eq!(stale_wire["lookupStatus"], "stale");
4130 assert!(stale_wire.get("lookup_status").is_none());
4131
4132 let fresh_lookup = omena_cascade_proof::DischargeLedgerLookupV0 {
4133 schema_version: "0",
4134 product: "omena-cascade-proof.discharge-ledger.lookup",
4135 cell_key: "fresh-cell".to_string(),
4136 status: omena_cascade_proof::DischargeLedgerLookupStatusV0::Matched,
4137 obligation_family: Some("ScopedMatching".to_string()),
4138 cell_family: Some("scope-flatten-candidate".to_string()),
4139 verdict: Some(omena_cascade_proof::DischargeLedgerVerdictV0::Accepted),
4140 boundedness_kind: Some("exact".to_string()),
4141 floor_reason: None,
4142 };
4143 let accepted_with_stamp = TransformCascadeProofObligationV0 {
4144 pass_id: "scope-flatten",
4145 proof_product: "omena-cascade.scope-flatten-proof",
4146 accepted: true,
4147 blocked_reason: None,
4148 provenance_preserved: true,
4149 cascade_safe_witness: "scope flatten proof accepted".to_string(),
4150 source_span_start: Some(0),
4151 source_span_end: Some(1),
4152 checked_obligations: vec!["rootScopeOnly"],
4153 canonical_smt_input: None,
4154 discharge_ledger_lookup: Some(fresh_lookup.clone()),
4155 discharge_evidence: Some(TransformDischargeEvidenceV0 {
4156 evidence_node_key: EvidenceNodeKeyV0::new(
4157 "omena-cascade-proof.cascade-proof-record",
4158 "fresh-obligation",
4159 ),
4160 guarantee_family: GuaranteeFamilyV0::LedgerBackedObligationDischarge,
4161 ledger_cell_key: "fresh-cell".to_string(),
4162 boundedness_kind: "exact".to_string(),
4163 }),
4164 proof_payload: serde_json::json!({ "accepted": true }),
4165 };
4166 let accepted_without_evidence = TransformCascadeProofObligationV0 {
4167 discharge_evidence: None,
4168 ..accepted_with_stamp.clone()
4169 };
4170 assert_eq!(
4171 flatten_discharge_precondition_failure(
4172 Some(TransformPassKind::ScopeFlatten),
4173 &[accepted_with_stamp.clone(), accepted_without_evidence],
4174 ),
4175 Some(TransformBlockedReasonV0::DischargeMissing {
4176 lookup_status: Some(DischargeLedgerLookupStatusV0::Matched),
4177 verdict: Some(DischargeLedgerVerdictV0::Accepted),
4178 })
4179 );
4180
4181 let mut mismatched_evidence = accepted_with_stamp.clone();
4182 if let Some(evidence) = mismatched_evidence.discharge_evidence.as_mut() {
4183 evidence.ledger_cell_key = "different-cell".to_string();
4184 }
4185 assert_eq!(
4186 flatten_discharge_precondition_failure(
4187 Some(TransformPassKind::ScopeFlatten),
4188 std::slice::from_ref(&mismatched_evidence),
4189 ),
4190 Some(TransformBlockedReasonV0::DischargeMissing {
4191 lookup_status: Some(DischargeLedgerLookupStatusV0::Matched),
4192 verdict: Some(DischargeLedgerVerdictV0::Accepted),
4193 })
4194 );
4195
4196 assert_eq!(
4197 flatten_discharge_precondition_failure(
4198 Some(TransformPassKind::ScopeFlatten),
4199 std::slice::from_ref(&accepted_with_stamp),
4200 ),
4201 None
4202 );
4203 assert_eq!(
4204 flatten_discharge_evidence(
4205 Some(TransformPassKind::ScopeFlatten),
4206 std::slice::from_ref(&accepted_with_stamp),
4207 )
4208 .len(),
4209 1
4210 );
4211 assert_eq!(
4212 flatten_discharge_precondition_failure(Some(TransformPassKind::NestingUnwrap), &[]),
4213 None
4214 );
4215 assert_eq!(
4216 flatten_discharge_precondition_failure(Some(TransformPassKind::LayerFlatten), &[]),
4217 Some(TransformBlockedReasonV0::DischargeMissing {
4218 lookup_status: None,
4219 verdict: None,
4220 })
4221 );
4222
4223 let execution = execute_transform_passes_on_source(
4224 "@scope (:root) { .card { color: red; } }",
4225 &[TransformPassKind::ScopeFlatten],
4226 );
4227 let applied = execution
4228 .decisions
4229 .iter()
4230 .find(|decision| decision.compatibility_outcome().pass_id == "scope-flatten");
4231 assert!(matches!(
4232 applied,
4233 Some(TransformDecision::Applied {
4234 discharge_evidence,
4235 ..
4236 }) if discharge_evidence.len() == 1
4237 && discharge_evidence[0].guarantee_family
4238 == GuaranteeFamilyV0::LedgerBackedObligationDischarge
4239 && discharge_evidence[0].ledger_cell_key.len() == 64
4240 ));
4241 let applied_wire = serde_json::to_value(applied);
4242 assert!(applied_wire.is_ok());
4243 let Ok(applied_wire) = applied_wire else {
4244 return;
4245 };
4246 assert_eq!(
4247 applied_wire["dischargeEvidence"][0]["boundednessKind"],
4248 "exact"
4249 );
4250 assert!(applied_wire.get("discharge_evidence").is_none());
4251 }
4252
4253 #[test]
4254 fn runtime_dispatch_entries_cover_public_registry_entries() {
4255 let registry = default_transform_pass_registry();
4256
4257 assert!(
4258 registry
4259 .entries
4260 .iter()
4261 .all(|entry| { runtime_pass_implementation_for_entry(entry).is_some() })
4262 );
4263 assert!(registry.entries.iter().all(|entry| {
4264 matches!(
4265 (
4266 entry.dispatch_kind,
4267 runtime_pass_implementation_for_entry(entry)
4268 ),
4269 (
4270 TransformPassDispatchKindV0::TextLocalSliceRewrite,
4271 Some(TransformRuntimePassImplementationV0::TextLocal(_))
4272 ) | (
4273 TransformPassDispatchKindV0::StructuralIrTransaction,
4274 Some(TransformRuntimePassImplementationV0::Structural(_))
4275 ) | (
4276 TransformPassDispatchKindV0::ModuleEvaluationHandler,
4277 Some(TransformRuntimePassImplementationV0::ModuleEvaluation(_))
4278 ) | (
4279 TransformPassDispatchKindV0::EmissionBoundary,
4280 Some(TransformRuntimePassImplementationV0::Emission(_))
4281 )
4282 )
4283 }));
4284 }
4285
4286 #[test]
4287 fn text_local_dispatch_handlers_declare_ir_window_scopes() {
4288 let handlers = text_local_pass_handlers();
4289
4290 assert_eq!(handlers.len(), 20);
4291 assert_eq!(
4292 handlers
4293 .iter()
4294 .filter(|handler| handler.window_scope
4295 == TransformTextLocalWindowScopeV0::DocumentTokenStream)
4296 .count(),
4297 2
4298 );
4299 assert_eq!(
4300 handlers
4301 .iter()
4302 .filter(|handler| handler.window_scope == TransformTextLocalWindowScopeV0::Selector)
4303 .count(),
4304 1
4305 );
4306 assert_eq!(
4307 handlers
4308 .iter()
4309 .filter(|handler| handler.window_scope
4310 == TransformTextLocalWindowScopeV0::DeclarationBlock)
4311 .count(),
4312 4
4313 );
4314 assert_eq!(
4315 handlers
4316 .iter()
4317 .filter(|handler| handler.window_scope
4318 == TransformTextLocalWindowScopeV0::DeclarationValue)
4319 .count(),
4320 13
4321 );
4322
4323 assert!(handlers.iter().any(|handler| {
4324 handler.kind == TransformPassKind::WhitespaceStrip
4325 && handler.window_scope == TransformTextLocalWindowScopeV0::DocumentTokenStream
4326 }));
4327 assert!(handlers.iter().any(|handler| {
4328 handler.kind == TransformPassKind::SelectorIsWhereCompression
4329 && handler.window_scope == TransformTextLocalWindowScopeV0::Selector
4330 }));
4331 assert!(handlers.iter().any(|handler| {
4332 handler.kind == TransformPassKind::ShorthandCombining
4333 && handler.window_scope == TransformTextLocalWindowScopeV0::DeclarationBlock
4334 }));
4335 assert!(handlers.iter().any(|handler| {
4336 handler.kind == TransformPassKind::CalcReduction
4337 && handler.window_scope == TransformTextLocalWindowScopeV0::DeclarationValue
4338 }));
4339 }
4340
4341 #[test]
4342 fn text_local_dispatch_handlers_declare_execution_modes() {
4343 let handlers = text_local_pass_handlers();
4344
4345 assert_eq!(handlers.len(), 20);
4346 assert_eq!(
4347 handlers
4348 .iter()
4349 .filter(|handler| handler.execution_mode
4350 == TransformTextLocalExecutionModeV0::FullDocument)
4351 .count(),
4352 6
4353 );
4354 assert_eq!(
4355 handlers
4356 .iter()
4357 .filter(|handler| handler.execution_mode
4358 == TransformTextLocalExecutionModeV0::WindowBatch)
4359 .count(),
4360 14
4361 );
4362
4363 assert!(handlers.iter().any(|handler| {
4364 handler.kind == TransformPassKind::ValueResolution
4365 && handler.execution_mode == TransformTextLocalExecutionModeV0::FullDocument
4366 }));
4367 assert!(handlers.iter().any(|handler| {
4368 handler.kind == TransformPassKind::CalcReduction
4369 && handler.execution_mode == TransformTextLocalExecutionModeV0::WindowBatch
4370 }));
4371 }
4372
4373 #[test]
4374 fn structural_dispatch_handlers_match_remaining_structural_descriptors() {
4375 let mut descriptor_pass_ids = default_transform_pass_descriptors()
4376 .into_iter()
4377 .filter(|descriptor| descriptor.pass_class == TransformPassClassV0::Structural)
4378 .map(|descriptor| descriptor.id)
4379 .collect::<Vec<_>>();
4380 let mut handler_pass_ids = structural_pass_handlers()
4381 .iter()
4382 .map(|handler| handler.kind.id())
4383 .collect::<Vec<_>>();
4384
4385 descriptor_pass_ids.sort_unstable();
4386 handler_pass_ids.sort_unstable();
4387
4388 assert_eq!(descriptor_pass_ids.len(), 21);
4389 assert_eq!(handler_pass_ids.len(), 21);
4390 assert_eq!(handler_pass_ids, descriptor_pass_ids);
4391 }
4392
4393 #[test]
4394 fn structural_dispatch_input_carries_ir_not_raw_css() -> Result<(), String> {
4395 let source = std::fs::read_to_string(
4396 std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
4397 .join("src")
4398 .join("runtime")
4399 .join("executor.rs"),
4400 )
4401 .map_err(|err| format!("executor source should be readable: {err:?}"))?;
4402 let input_anchor = source
4403 .find("struct TransformStructuralPassInputV0")
4404 .ok_or_else(|| "structural input should exist".to_string())?;
4405 let handler_anchor = source[input_anchor..]
4406 .find("fn structural_pass_handlers")
4407 .ok_or_else(|| "structural handler boundary should exist".to_string())?;
4408 let input_body = &source[input_anchor..input_anchor + handler_anchor];
4409
4410 assert!(input_body.contains("current_ir: &'a mut TransformIrV0"));
4411 assert!(!input_body.contains("input_css:"));
4412 assert!(!input_body.contains("fn source_text(&self) -> &str"));
4413 assert!(input_body.contains("fn current_ir_mut(&mut self) -> &mut TransformIrV0"));
4414 Ok(())
4415 }
4416
4417 #[test]
4418 fn text_local_dispatch_uses_ir_window_input() -> Result<(), String> {
4419 let source = std::fs::read_to_string(
4420 std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
4421 .join("src")
4422 .join("runtime")
4423 .join("executor.rs"),
4424 )
4425 .map_err(|err| format!("executor source should be readable: {err:?}"))?;
4426 let runner_anchor = source
4427 .find("type TransformTextLocalRunnerV0")
4428 .ok_or_else(|| "text-local runner type should exist".to_string())?;
4429 let structural_runner_anchor = source[runner_anchor..]
4430 .find("type TransformStructuralRunnerV0")
4431 .ok_or_else(|| "structural runner type should delimit text-local input".to_string())?;
4432 let text_local_input_body =
4433 &source[runner_anchor..runner_anchor + structural_runner_anchor];
4434
4435 assert!(text_local_input_body.contains("TransformTextLocalPassInputV0<'a>"));
4436 assert!(text_local_input_body.contains("TransformTextLocalPassOutputV0<'a>"));
4437 assert!(text_local_input_body.contains("TransformTextLocalIrWindowV0"));
4438 assert!(!text_local_input_body.contains("fn(&str, StyleDialect"));
4439 assert!(text_local_input_body.contains("fn rewrite_windows("));
4440 assert!(text_local_input_body.contains("fn rewrite_full_document("));
4441
4442 let dispatch_anchor = source
4443 .find("fn dispatch_text_local_pass")
4444 .ok_or_else(|| "text-local dispatch should exist".to_string())?;
4445 let whitespace_anchor = source[dispatch_anchor..]
4446 .find("fn run_whitespace_strip_text_local")
4447 .ok_or_else(|| "first text-local runner should delimit dispatch".to_string())?;
4448 let dispatch_body = &source[dispatch_anchor..dispatch_anchor + whitespace_anchor];
4449
4450 assert!(dispatch_body.contains("current_ir: &TransformIrV0"));
4451 assert!(dispatch_body.contains("TransformTextLocalPassInputV0::from_ir("));
4452 assert!(dispatch_body.contains("handler.window_scope"));
4453 assert!(dispatch_body.contains("handler.execution_mode"));
4454 assert!(!dispatch_body.contains("input_css: &str"));
4455
4456 let loop_anchor = source
4457 .find("Some(TransformRuntimePassImplementationV0::TextLocal(handler))")
4458 .ok_or_else(|| "text-local executor dispatch branch should exist".to_string())?;
4459 let module_anchor = source[loop_anchor..]
4460 .find("Some(TransformRuntimePassImplementationV0::ModuleEvaluation(pass))")
4461 .ok_or_else(|| "module branch should delimit text-local branch".to_string())?;
4462 let loop_body = &source[loop_anchor..loop_anchor + module_anchor];
4463
4464 assert!(loop_body.contains("dispatch_text_local_pass("));
4465 assert!(loop_body.contains("&document.current_ir"));
4466 assert!(!loop_body.contains("pass_input_css, dialect, context"));
4467 Ok(())
4468 }
4469
4470 #[test]
4471 fn text_local_declaration_scopes_rewrite_multiple_ir_windows() {
4472 let source = ".a { color: RED; } .b { color: BLUE; }";
4473 let ir = lower_transform_ir_from_source(source, StyleDialect::Css, "window-batch-test");
4474 let context = TransformExecutionContextV0::default();
4475 let input = TransformTextLocalPassInputV0::from_ir(
4476 &ir,
4477 TransformTextLocalWindowScopeV0::DeclarationValue,
4478 TransformTextLocalExecutionModeV0::WindowBatch,
4479 StyleDialect::Css,
4480 &context,
4481 );
4482
4483 assert_eq!(input.source_windows.len(), 2);
4484
4485 let output = input.rewrite_text_local(|window_source, _dialect, _context| {
4486 (window_source.to_ascii_lowercase(), 1)
4487 });
4488
4489 assert_eq!(output.input_byte_len(), source.len());
4490 assert_eq!(output.mutation_count, 2);
4491 assert_eq!(output.provenance_mutation_spans().len(), 2);
4492 assert_eq!(
4493 &source[output.provenance_mutation_spans()[0].source_span_start
4494 ..output.provenance_mutation_spans()[0].source_span_end],
4495 "RED"
4496 );
4497 assert_eq!(
4498 &output.rewritten_css[output.provenance_mutation_spans()[0].generated_span_start
4499 ..output.provenance_mutation_spans()[0].generated_span_end],
4500 "red"
4501 );
4502 assert_eq!(
4503 &source[output.provenance_mutation_spans()[1].source_span_start
4504 ..output.provenance_mutation_spans()[1].source_span_end],
4505 "BLUE"
4506 );
4507 assert_eq!(
4508 &output.rewritten_css[output.provenance_mutation_spans()[1].generated_span_start
4509 ..output.provenance_mutation_spans()[1].generated_span_end],
4510 "blue"
4511 );
4512 assert_eq!(
4513 output.into_document_css(),
4514 ".a { color: red; } .b { color: blue; }"
4515 );
4516 }
4517
4518 #[test]
4519 fn structural_dispatch_handlers_commit_through_ir_mutation_only() -> Result<(), String> {
4520 let source = std::fs::read_to_string(
4521 std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
4522 .join("src")
4523 .join("runtime")
4524 .join("executor.rs"),
4525 )
4526 .map_err(|err| format!("executor source should be readable: {err:?}"))?;
4527 let first_structural_handler = source
4528 .find("fn run_import_inline_structural")
4529 .ok_or_else(|| "first structural handler should exist".to_string())?;
4530 let executor_loop_anchor = source[first_structural_handler..]
4531 .find("fn execute_transform_passes_on_source_with_active_lex_cache")
4532 .ok_or_else(|| "executor loop should delimit structural handlers".to_string())?;
4533 let structural_handler_body =
4534 &source[first_structural_handler..first_structural_handler + executor_loop_anchor];
4535 let ir_mutation_count = structural_handler_body
4536 .matches("input.ir_mutation_result(")
4537 .count();
4538
4539 assert_eq!(ir_mutation_count, structural_pass_handlers().len());
4540 assert!(
4541 !structural_handler_body.contains("TransformPassDispatchResultV0::textual_mutation(")
4542 );
4543 assert!(!structural_handler_body.contains("TransformPassDispatchResultV0::ir_mutation("));
4544 assert!(!structural_handler_body.contains("_rendered_css"));
4545 assert!(!structural_handler_body.contains("input.source_text("));
4546 Ok(())
4547 }
4548
4549 #[test]
4550 fn structural_ir_mutations_do_not_relower_document_from_css() -> Result<(), String> {
4551 let source = std::fs::read_to_string(
4552 std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
4553 .join("src")
4554 .join("runtime")
4555 .join("executor.rs"),
4556 )
4557 .map_err(|err| format!("executor source should be readable: {err:?}"))?;
4558 let ir_mutation_anchor = source
4559 .find("fn ir_mutation(")
4560 .ok_or_else(|| "IR mutation dispatch result constructor should exist".to_string())?;
4561 let planned_only_anchor = source[ir_mutation_anchor..]
4562 .find("fn from_mutation_outcome(")
4563 .ok_or_else(|| {
4564 "mutation decision constructor should delimit IR mutation".to_string()
4565 })?;
4566 let ir_mutation_body =
4567 &source[ir_mutation_anchor..ir_mutation_anchor + planned_only_anchor];
4568
4569 assert!(ir_mutation_body.contains("result.document_ir_updated = true;"));
4570
4571 let update_anchor = source
4572 .find("if document_ir_updated")
4573 .ok_or_else(|| "executor should branch structural IR updates".to_string())?;
4574 let outcomes_anchor = source[update_anchor..]
4575 .find("outcomes.push(outcome);")
4576 .ok_or_else(|| "outcome push should delimit document update".to_string())?;
4577 let update_body = &source[update_anchor..update_anchor + outcomes_anchor];
4578 let text_branch_anchor = update_body
4579 .find("match next_textual_css")
4580 .ok_or_else(|| "text-local/module output branch should exist".to_string())?;
4581 let relower_anchor = update_body
4582 .find("document.replace_with_css(next_css);")
4583 .ok_or_else(|| {
4584 "text-local/module document re-lowering path should exist".to_string()
4585 })?;
4586 let ir_branch_body = &update_body[..text_branch_anchor];
4587
4588 assert!(relower_anchor > text_branch_anchor);
4589 assert!(!ir_branch_body.contains("document.replace_with_css(next_css);"));
4590 Ok(())
4591 }
4592
4593 #[test]
4594 fn executor_loop_materializes_textual_bridge_lazily() -> Result<(), String> {
4595 let source = std::fs::read_to_string(
4596 std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
4597 .join("src")
4598 .join("runtime")
4599 .join("executor.rs"),
4600 )
4601 .map_err(|err| format!("executor source should be readable: {err:?}"))?;
4602 let loop_anchor = source
4603 .find("for (pass_index, pass_id) in ordered_pass_ids.iter().enumerate()")
4604 .ok_or_else(|| "executor pass loop should exist".to_string())?;
4605 let outcomes_anchor = source[loop_anchor..]
4606 .find("outcomes.push(outcome);")
4607 .ok_or_else(|| "outcome push should delimit pass loop body".to_string())?;
4608 let loop_body = &source[loop_anchor..loop_anchor + outcomes_anchor];
4609
4610 assert!(loop_body.contains("TransformTextualBridgeSnapshotV0::default()"));
4611 assert!(loop_body.contains("textual_bridge.materialize_current_css(&document);"));
4612 assert!(loop_body.contains("if should_maintain_document_lex_cache"));
4613 assert!(loop_body.contains("collect_cascade_proof_obligations_for_ir_pass_input("));
4614 assert!(loop_body.contains("dispatch_structural_pass("));
4615 assert!(loop_body.contains("Some(mutation_spans) => mutation_spans"));
4616 assert!(loop_body.contains("if document_ir_updated"));
4617 assert!(loop_body.contains("let output_byte_len = document.current_byte_len();"));
4618 assert!(loop_body.contains("let next_css = document.current_css().to_string();"));
4619 assert!(loop_body.contains("match next_textual_css"));
4620 assert!(loop_body.contains("derive_transform_mutation_spans(pass_input_css, &next_css)"));
4621 assert!(loop_body.contains("outcome_mutation_spans.push(Vec::new());"));
4622 Ok(())
4623 }
4624
4625 #[test]
4626 fn structural_single_transaction_uses_ir_mutation_span_envelope() -> Result<(), String> {
4627 let source = ".button { color: red; }";
4628 let selector_end = source
4629 .find('{')
4630 .ok_or_else(|| "fixture should contain a block".to_string())?;
4631 let context = TransformExecutionContextV0 {
4632 class_name_rewrites: vec![crate::TransformClassNameRewriteV0 {
4633 original_name: "button".to_string(),
4634 rewritten_name: "_button_x".to_string(),
4635 }],
4636 ..TransformExecutionContextV0::default()
4637 };
4638 let execution = execute_transform_passes_on_source_with_dialect_and_context(
4639 source,
4640 StyleDialect::Css,
4641 &[TransformPassKind::HashCssModuleClassNames],
4642 &context,
4643 );
4644 let mutation_spans = &execution.provenance_derivation_forest.nodes[0].mutation_spans;
4645
4646 assert_eq!(execution.output_css, "._button_x{ color: red; }");
4647 assert_eq!(mutation_spans.len(), 1);
4648 assert_eq!(mutation_spans[0].source_span_start, 0);
4649 assert_eq!(mutation_spans[0].source_span_end, selector_end);
4650 assert!(mutation_spans[0].source_span_end < source.len());
4651 Ok(())
4652 }
4653
4654 #[test]
4655 fn structural_multi_transaction_batches_compose_mutation_span_coordinates() {
4656 let spans = compose_ir_transaction_mutation_span_batches(
4657 6,
4658 10,
4659 &[
4660 vec![mutation_span(1, 3, 1, 5)],
4661 vec![mutation_span(6, 7, 6, 9)],
4662 ],
4663 );
4664
4665 assert_eq!(
4666 spans,
4667 Some(vec![mutation_span(1, 3, 1, 5), mutation_span(4, 5, 6, 9)])
4668 );
4669 }
4670
4671 #[test]
4672 fn structural_multi_transaction_batches_fall_back_when_coordinates_do_not_project() {
4673 let spans = compose_ir_transaction_mutation_span_batches(
4674 6,
4675 8,
4676 &[
4677 vec![mutation_span(1, 3, 1, 5)],
4678 vec![mutation_span(4, 6, 4, 6)],
4679 ],
4680 );
4681
4682 assert_eq!(spans, None);
4683 }
4684
4685 #[test]
4686 fn structural_multi_transaction_pass_uses_composed_ir_mutation_spans() {
4687 let source = ".a { color: red; color: blue; }.dup { margin: 0; }.dup { margin: 0; }";
4688 let execution = execute_transform_passes_on_source_with_dialect_and_context(
4689 source,
4690 StyleDialect::Css,
4691 &[TransformPassKind::RuleDeduplication],
4692 &TransformExecutionContextV0::default(),
4693 );
4694 let mutation_spans = &execution.provenance_derivation_forest.nodes[0].mutation_spans;
4695
4696 assert_eq!(
4697 execution
4698 .structural_ir_transaction_telemetry
4699 .transaction_commit_count,
4700 2
4701 );
4702 let ir_telemetry = execution.structural_ir_transaction_telemetry;
4703 assert_eq!(ir_telemetry.ir_transaction_commit_count, 2);
4704 assert_eq!(ir_telemetry.ir_materialization_count, 2);
4705 assert_eq!(ir_telemetry.ir_metadata_refresh_count, 4);
4706 assert_eq!(ir_telemetry.ir_mutation_count, 2);
4707 assert_eq!(
4708 ir_telemetry.ir_metadata_refresh_count,
4709 ir_telemetry.ir_transaction_commit_count + ir_telemetry.ir_materialization_count,
4710 "each metadata refresh must be attributed to one successful commit or materialization"
4711 );
4712 assert_eq!(
4713 ir_telemetry.ir_transaction_commit_count,
4714 execution
4715 .structural_ir_transaction_telemetry
4716 .transaction_commit_count,
4717 "this helper-only fixture pins the corollary without conflating the two commit domains"
4718 );
4719 assert_eq!(execution.mutation_count, 2);
4720 assert_eq!(mutation_spans.len(), 2);
4721 assert!(mutation_spans.iter().all(|span| {
4722 span.source_span_start <= span.source_span_end
4723 && span.generated_span_start <= span.generated_span_end
4724 && span.generated_span_end <= execution.output_css.len()
4725 }));
4726 }
4727
4728 #[test]
4729 fn lex_cache_consumer_classification_stays_text_local() {
4730 for descriptor in default_transform_pass_descriptors() {
4731 assert_eq!(
4732 transform_pass_may_consume_lex_cache(descriptor.kind),
4733 descriptor.pass_class == TransformPassClassV0::TextLocal,
4734 "unexpected lex-cache consumer classification for {}",
4735 descriptor.id
4736 );
4737 }
4738 }
4739
4740 #[test]
4741 fn structural_dispatch_results_never_return_textual_css() -> Result<(), String> {
4742 let source = r#"
4743@import "./tokens.css";
4744@layer theme { .card { color: red; } }
4745@scope (:root) { .scoped { color: red; } }
4746.dup { color: red; } .dup { color: red; }
4747.empty {}
4748@supports not (display: grid) { .supports-dead { color: red; } }
4749@media not all { .media-dead { color: red; } }
4750@container (max-width: -1px) { .container-dead { color: red; } }
4751@when supports(display: grid) { .grid { display: grid; } } @else { .fallback { display: block; } }
4752.button { composes: base utility; color: var(--pkg-brand); }
4753.base { color: blue; } .utility { color: green; }
4754.used { animation: spin 1s; --keep: green; color: var(--keep); }
4755.unused { color: red; --dead: red; }
4756@keyframes spin { to { opacity: 1; } }
4757@keyframes fade { to { opacity: 0; } }
4758@value keep: 1px; @value dead: 2px; :export { keepExport: keep; deadExport: dead; }
4759"#;
4760 let context = TransformExecutionContextV0 {
4761 import_inlines: vec![TransformImportInlineV0 {
4762 import_source: "./tokens.css".to_string(),
4763 replacement_css: ":root { --pkg-brand: #123456; }".to_string(),
4764 }],
4765 class_name_rewrites: vec![
4766 crate::TransformClassNameRewriteV0 {
4767 original_name: "button".to_string(),
4768 rewritten_name: "_button_hash".to_string(),
4769 },
4770 crate::TransformClassNameRewriteV0 {
4771 original_name: "base".to_string(),
4772 rewritten_name: "_base_hash".to_string(),
4773 },
4774 crate::TransformClassNameRewriteV0 {
4775 original_name: "utility".to_string(),
4776 rewritten_name: "_utility_hash".to_string(),
4777 },
4778 ],
4779 css_module_composes_resolutions: vec![TransformCssModuleComposesResolutionV0 {
4780 local_class_name: "button".to_string(),
4781 exported_class_names: vec!["base".to_string(), "utility".to_string()],
4782 }],
4783 design_token_routes: vec![TransformDesignTokenRouteV0 {
4784 token_name: "--pkg-brand".to_string(),
4785 routed_value: "#123456".to_string(),
4786 }],
4787 ..TransformExecutionContextV0::default()
4788 };
4789 let bundle = structural_dispatch_fixture_bundle()?;
4790
4791 for handler in structural_pass_handlers() {
4792 let mut ir = lower_transform_ir_from_source(
4793 source,
4794 StyleDialect::Css,
4795 "omena-transform-passes.structural-dispatch-fixture",
4796 );
4797 let input_byte_len = ir.source_text().len();
4798 let decision_policy = transform_structural_decision_policy(handler.kind)
4799 .ok_or_else(|| format!("missing decision policy for {}", handler.kind.id()))?;
4800 let result = (handler.run)(TransformStructuralPassInputV0 {
4801 pass_id: handler.kind.id(),
4802 kind: handler.kind,
4803 decision_policy,
4804 reachability_precision: FactPrecision::Conservative,
4805 current_ir: &mut ir,
4806 input_byte_len,
4807 dialect: StyleDialect::Css,
4808 context: &context,
4809 closed_world_bundle: Some(&bundle),
4810 module_qualified_symbols: None,
4811 module_reachable_class_names: None,
4812 });
4813
4814 assert!(
4815 result.next_textual_css.is_none(),
4816 "structural handler {} must not return textual CSS",
4817 handler.kind.id()
4818 );
4819 if result.decision.compatibility_outcome().mutation_count > 0 {
4820 assert!(
4821 result.document_ir_updated,
4822 "structural handler {} reported a mutation without an IR update",
4823 handler.kind.id()
4824 );
4825 }
4826 }
4827 Ok(())
4828 }
4829
4830 #[test]
4831 fn semantic_preservation_refuses_mismatching_structural_rewrite() {
4832 let input_css = ".card { color: red; }";
4833 let mut document = TransformExecutionDocumentV0::new(input_css, StyleDialect::Css);
4834 let input_ir = document.current_ir.clone();
4835 document.replace_with_css(".card { color: blue; }".to_string());
4836 let output_byte_len = document.current_byte_len();
4837 let mut telemetry = TransformSemanticPreservationTelemetryV0::default();
4838 let dispatch_result = TransformPassDispatchResultV0::ir_mutation(
4839 TransformPassKind::RuleDeduplication.id(),
4840 input_css.len(),
4841 output_byte_len,
4842 1,
4843 "test structural rewrite",
4844 );
4845
4846 let enforcement_context = TransformSemanticPreservationEnforcementContextV0 {
4847 closed_world_bundle: None,
4848 module_qualified_symbols: None,
4849 module_reachable_class_names: None,
4850 projection: &SemanticObservationProjectionV0::default(),
4851 mutation_spans: &[TransformProvenanceMutationSpanV0 {
4852 source_span_start: 0,
4853 source_span_end: input_css.len(),
4854 generated_span_start: 0,
4855 generated_span_end: output_byte_len,
4856 node_key: None,
4857 }],
4858 cascade_environment: None,
4859 observe_pending_textual_output: false,
4860 };
4861 let (checked, winner_equality) = enforce_semantic_preservation_for_dispatch_result(
4862 TransformPassKind::RuleDeduplication,
4863 &input_ir,
4864 &mut document,
4865 dispatch_result,
4866 &mut telemetry,
4867 enforcement_context,
4868 );
4869
4870 assert!(winner_equality.is_none());
4871 assert_eq!(document.current_css(), input_css);
4872 assert!(!checked.document_ir_updated);
4873 assert!(matches!(
4874 checked.decision,
4875 TransformDecisionDraftV0::Rejected {
4876 reason: TransformRejectionReasonV0::SemanticPreservation,
4877 ..
4878 }
4879 ));
4880 assert_eq!(
4881 checked.decision.compatibility_outcome().status,
4882 TransformPassRuntimeStatus::PlannedOnly
4883 );
4884 assert_eq!(checked.decision.compatibility_outcome().mutation_count, 0);
4885 assert_eq!(telemetry.observed_pass_count, 1);
4886 assert_eq!(telemetry.preserved_pass_count, 0);
4887 assert_eq!(telemetry.blocked_pass_count, 1);
4888 }
4889
4890 #[test]
4891 fn semantic_trust_is_observational_to_applied_decisions() {
4892 let outcome = TransformPassExecutionOutcomeV0 {
4893 pass_id: "empty-rule-removal",
4894 status: TransformPassRuntimeStatus::Applied,
4895 input_byte_len: 12,
4896 output_byte_len: 0,
4897 mutation_count: 1,
4898 provenance_preserved: true,
4899 detail: "removed an empty rule",
4900 };
4901 let without_trust = TransformDecisionDraftV0::Applied {
4902 outcome: outcome.clone(),
4903 }
4904 .finalize("input".to_string(), "output".to_string(), Vec::new(), None);
4905 let with_trust = TransformDecisionDraftV0::Applied { outcome }.finalize(
4906 "input".to_string(),
4907 "output".to_string(),
4908 Vec::new(),
4909 Some(TransformSemanticGuaranteeTierV0::L0Observed),
4910 );
4911
4912 assert_eq!(
4913 without_trust.compatibility_outcome(),
4914 with_trust.compatibility_outcome()
4915 );
4916 assert_eq!(
4917 without_trust.rollback_receipt(),
4918 with_trust.rollback_receipt()
4919 );
4920 assert!(without_trust.semantic_guarantee_tier().is_none());
4921 assert_eq!(
4922 with_trust.semantic_guarantee_tier(),
4923 Some(&TransformSemanticGuaranteeTierV0::L0Observed)
4924 );
4925 }
4926
4927 #[test]
4928 fn rollback_receipts_distinguish_committed_rewrites_from_rejected_transactions()
4929 -> Result<(), String> {
4930 let source = ".card { color: red; } .card { color: red; }";
4931 let summary =
4932 execute_transform_passes_on_source(source, &[TransformPassKind::RuleDeduplication]);
4933 let applied_receipt = summary
4934 .decisions
4935 .first()
4936 .and_then(TransformDecision::rollback_receipt)
4937 .ok_or_else(|| "applied rewrite should carry a rollback receipt".to_string())?;
4938 assert_eq!(
4939 applied_receipt.restorable,
4940 RollbackScopeV0::CommittedIrrecoverable
4941 );
4942 assert_eq!(applied_receipt.attempted_mutation_count, Some(1));
4943 assert_eq!(
4944 applied_receipt.input_content_signature,
4945 transform_content_signature(source)
4946 );
4947 assert!(applied_receipt.output_preserved_content_signature.is_none());
4948
4949 let rejected_source = ".card { color: tokens.$accent; }";
4950 let mut rejected_ir = lower_transform_ir_from_source(
4951 rejected_source,
4952 StyleDialect::Scss,
4953 "rollback-rejected-transaction",
4954 );
4955 let rule = rejected_ir
4956 .nodes
4957 .iter()
4958 .find(|node| node.kind == IrNodeKindV0::StyleRule)
4959 .map(|node| node.node_id)
4960 .ok_or_else(|| "rejection fixture should contain a style rule".to_string())?;
4961 let input_signature = transform_content_signature(rejected_ir.source_text());
4962 let region = IrEditRegionV0::full(rejected_ir.source_byte_len);
4963 let mut transaction =
4964 IrTransactionV0::new(&mut rejected_ir, "rollback-rejected-transaction", region);
4965 transaction
4966 .replace_node(rule, ".card { color: blue; }")
4967 .map_err(|error| format!("{error:?}"))?;
4968 assert!(transaction.commit().is_err());
4969 assert_eq!(rejected_ir.source_text(), rejected_source);
4970
4971 let rejected_decision = TransformDecisionDraftV0::Rejected {
4972 reason: TransformRejectionReasonV0::IrTransaction {
4973 pass: TransformPassKind::RuleDeduplication,
4974 },
4975 outcome: planned_only_outcome(
4976 TransformPassKind::RuleDeduplication.id(),
4977 rejected_source.len(),
4978 rejected_source.len(),
4979 "transaction preserved its input",
4980 ),
4981 }
4982 .finalize(
4983 input_signature,
4984 transform_content_signature(rejected_ir.source_text()),
4985 Vec::new(),
4986 None,
4987 );
4988 let rejected_receipt = rejected_decision
4989 .rollback_receipt()
4990 .ok_or_else(|| "rejected rewrite should carry a rollback receipt".to_string())?;
4991 assert!(rejected_receipt.preserves_rejected_input());
4992 assert_eq!(rejected_receipt.attempted_mutation_count, None);
4993
4994 let mut perturbed_receipt = rejected_receipt.clone();
4995 perturbed_receipt.output_preserved_content_signature =
4996 Some(transform_content_signature(".card { color: blue; }"));
4997 assert!(!perturbed_receipt.preserves_rejected_input());
4998
4999 let shadow =
5000 crate::runtime::structural_shadow::summarize_structural_ir_shadow_equivalence_v0();
5001 assert!(shadow.fixture_count > 0);
5002 assert!(shadow.all_fields_match);
5003 assert!(shadow.all_typed_path_fields_match);
5004 Ok(())
5005 }
5006
5007 #[test]
5008 fn semantic_preservation_counts_executed_simple_structural_pass() {
5009 let execution = execute_transform_passes_on_source(
5010 ".dup { color: red; }.dup { color: red; }",
5011 &[TransformPassKind::RuleDeduplication],
5012 );
5013
5014 assert_eq!(
5015 execution
5016 .semantic_preservation_telemetry
5017 .observed_pass_count,
5018 1
5019 );
5020 assert_eq!(
5021 execution
5022 .semantic_preservation_telemetry
5023 .preserved_pass_count,
5024 1
5025 );
5026 assert_eq!(
5027 execution.semantic_preservation_telemetry.blocked_pass_count,
5028 0
5029 );
5030 }
5031
5032 fn structural_dispatch_fixture_bundle() -> Result<ClosedWorldBundleV0, String> {
5033 let instance = ModuleInstanceKeyV0::new(
5034 ModuleIdV0::new("omena-transform-passes.structural-dispatch-fixture"),
5035 ConfigurationHashV0::none(),
5036 );
5037 let module = ClosedWorldLinkedModuleV0::new(instance.clone())
5038 .with_class_name("used")
5039 .with_class_name("button")
5040 .with_class_name("base")
5041 .with_class_name("utility")
5042 .with_keyframe_name("spin")
5043 .with_value_name("keepExport")
5044 .with_custom_property_name("keepExport");
5045 ClosedWorldBundleV0::try_from_linked_modules(vec![instance], vec![module])
5046 .map_err(|err| format!("structural dispatch fixture bundle should be valid: {err:?}"))
5047 }
5048
5049 #[test]
5050 fn structural_only_execution_never_records_lex_cache_full_relex_fallback() {
5051 let structural_passes = default_transform_pass_descriptors()
5052 .into_iter()
5053 .filter(|descriptor| descriptor.pass_class == TransformPassClassV0::Structural)
5054 .map(|descriptor| descriptor.kind)
5055 .collect::<Vec<_>>();
5056 let source =
5057 ".dup { color: red; }.dup { color: red; }.empty { } @media screen { .media { } }";
5058
5059 super::super::lex_cache::reset_transform_lex_cache_splice_telemetry();
5060 let execution = execute_transform_passes_on_source_with_dialect_and_context(
5061 source,
5062 StyleDialect::Css,
5063 structural_passes.as_slice(),
5064 &TransformExecutionContextV0::default(),
5065 );
5066 let telemetry = super::super::lex_cache::transform_lex_cache_splice_telemetry_snapshot();
5067
5068 assert_eq!(structural_passes.len(), 21);
5069 assert!(execution.mutation_count > 0);
5070 assert!(
5071 execution
5072 .structural_ir_transaction_telemetry
5073 .transaction_commit_count
5074 > 0
5075 );
5076 assert_eq!(telemetry.full_relex_fallback_count, 0);
5077 assert_eq!(telemetry.window_derivation_fallback_count, 0);
5078 assert_eq!(telemetry.full_output_window_fallback_count, 0);
5079 assert_eq!(telemetry.token_offset_fallback_count, 0);
5080 }
5081
5082 #[test]
5083 fn structural_execution_hashes_less_class_names_through_ir_transactions() {
5084 let context = TransformExecutionContextV0 {
5085 class_name_rewrites: vec![crate::TransformClassNameRewriteV0 {
5086 original_name: "button".to_string(),
5087 rewritten_name: "_button_x".to_string(),
5088 }],
5089 ..TransformExecutionContextV0::default()
5090 };
5091 let execution = execute_transform_passes_on_source_with_dialect_and_context(
5092 ".button { color: red; }",
5093 StyleDialect::Less,
5094 &[
5095 TransformPassKind::HashCssModuleClassNames,
5096 TransformPassKind::PrintCss,
5097 ],
5098 &context,
5099 );
5100
5101 assert_eq!(execution.output_css, "._button_x{ color: red; }");
5102 assert!(
5103 execution
5104 .structural_ir_transaction_telemetry
5105 .transaction_commit_count
5106 > 0
5107 );
5108 }
5109
5110 #[test]
5111 fn executor_loop_dispatches_without_pass_kind_match() -> Result<(), String> {
5112 let source = std::fs::read_to_string(
5113 std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
5114 .join("src")
5115 .join("runtime")
5116 .join("executor.rs"),
5117 )
5118 .map_err(|err| format!("executor source should be readable: {err:?}"))?;
5119 let loop_anchor = source
5120 .find("let mut dispatch_result =")
5121 .ok_or_else(|| "executor should keep a dispatch result boundary".to_string())?;
5122 let loop_match_tail = &source[loop_anchor..];
5123 let destructure_anchor = loop_match_tail
5124 .find("let TransformPassDispatchResultV0")
5125 .ok_or_else(|| "executor should destructure the dispatch result".to_string())?;
5126 let loop_dispatch_body = &loop_match_tail[..destructure_anchor];
5127
5128 assert!(loop_dispatch_body.contains("dispatch_text_local_pass"));
5129 assert!(loop_dispatch_body.contains("dispatch_module_evaluation_pass"));
5130 assert!(loop_dispatch_body.contains("dispatch_structural_pass"));
5131 assert!(loop_dispatch_body.contains("dispatch_emission_pass"));
5132 assert!(loop_dispatch_body.contains("TransformRuntimePassImplementationV0::Structural"));
5133 assert!(
5134 source.contains("runtime_pass_entry_for_kind(kind, pass_registry.entries.as_slice())")
5135 );
5136 assert!(!loop_dispatch_body.contains("ModuleEvaluationOrEgressHandler"));
5137 assert!(!loop_dispatch_body.contains("StructuralHandler"));
5138 assert!(!loop_dispatch_body.contains("match pass"));
5139 assert!(!loop_dispatch_body.contains("Some(TransformPassKind::"));
5140 Ok(())
5141 }
5142}
5143
5144#[cfg(test)]
5145mod module_evaluation_materialization_tests {
5146 use super::*;
5147 use crate::model::TransformModuleEvaluationOracleV0;
5148
5149 fn oracle_allowing_native_output() -> TransformModuleEvaluationOracleV0 {
5150 TransformModuleEvaluationOracleV0 {
5151 mode: "oracleOnly".to_string(),
5152 product_output_source: "legacyEvaluatedCss".to_string(),
5153 divergence_count: 0,
5154 all_legacy_declaration_values_preserved: true,
5155 ..TransformModuleEvaluationOracleV0::default()
5156 }
5157 }
5158
5159 fn module_evaluation(
5160 evaluated_css: &str,
5161 native_edit_output: Option<&str>,
5162 oracle: Option<TransformModuleEvaluationOracleV0>,
5163 ) -> TransformModuleEvaluationV0 {
5164 TransformModuleEvaluationV0 {
5165 evaluator: "test".to_string(),
5166 product_output_source: Some("nativeEditOutput".to_string()),
5167 evaluated_css: evaluated_css.to_string(),
5168 native_edit_output: native_edit_output.map(str::to_string),
5169 native_replacements: Vec::new(),
5170 native_edits: Vec::new(),
5171 oracle,
5172 }
5173 }
5174
5175 #[test]
5176 fn module_evaluation_consumes_oracle_backed_matching_native_output() {
5177 let input_css = ".input { color: red; }";
5178 let evaluation = module_evaluation(
5179 ".native { color: red; }",
5180 Some(".native { color: red; }"),
5181 Some(oracle_allowing_native_output()),
5182 );
5183
5184 let output = materialize_transform_module_evaluation_output(
5185 input_css,
5186 &evaluation,
5187 "native",
5188 "preserve",
5189 );
5190
5191 assert_eq!(output.css, ".native { color: red; }");
5192 assert_eq!(output.detail, "native");
5193 }
5194
5195 #[test]
5196 fn module_evaluation_preserves_input_when_oracle_backed_native_output_mismatches() {
5197 let input_css = ".input { color: red; }";
5198 let evaluation = module_evaluation(
5199 ".legacy { color: red; }",
5200 Some(".native { color: red; }"),
5201 Some(oracle_allowing_native_output()),
5202 );
5203
5204 let output = materialize_transform_module_evaluation_output(
5205 input_css,
5206 &evaluation,
5207 "native",
5208 "preserve",
5209 );
5210
5211 assert_eq!(output.css, input_css);
5212 assert_eq!(output.detail, "preserve");
5213 }
5214}
5215
5216#[cfg(test)]
5217mod coordinate_map_tests {
5218 use super::*;
5219
5220 fn mutation_span(
5221 source_span_start: usize,
5222 source_span_end: usize,
5223 generated_span_start: usize,
5224 generated_span_end: usize,
5225 ) -> TransformProvenanceMutationSpanV0 {
5226 TransformProvenanceMutationSpanV0 {
5227 source_span_start,
5228 source_span_end,
5229 generated_span_start,
5230 generated_span_end,
5231 node_key: None,
5232 }
5233 }
5234
5235 #[test]
5240 fn coordinate_map_remaps_post_mutation_span_to_original_after_one_pass() {
5241 let mut map = TransformSpanCoordinateMapV0::new(6);
5243 map.apply_mutation_spans(&[mutation_span(1, 3, 1, 5)]);
5244 assert_eq!(map.map_current_span_to_original(5, 7), Some((3, 5)));
5246 assert_eq!(map.map_current_span_to_original(0, 1), Some((0, 1)));
5248 }
5249
5250 #[test]
5252 fn coordinate_map_composes_across_two_mutating_passes() {
5253 let mut map = TransformSpanCoordinateMapV0::new(6); map.apply_mutation_spans(&[mutation_span(1, 3, 1, 5)]);
5256 map.apply_mutation_spans(&[mutation_span(6, 7, 6, 9)]);
5258 assert_eq!(map.map_current_span_to_original(9, 10), Some((5, 6)));
5260 assert_eq!(map.map_current_span_to_original(0, 1), Some((0, 1)));
5261 }
5262
5263 #[test]
5266 fn coordinate_map_returns_none_for_post_mutation_straddling_span() {
5267 let mut map = TransformSpanCoordinateMapV0::new(6);
5268 map.apply_mutation_spans(&[mutation_span(1, 3, 1, 5)]);
5269 assert_eq!(map.map_current_span_to_original(0, 7), None);
5271 }
5272}