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