Skip to main content

omena_transform_cst/
pass_descriptor.rs

1//! Transform pass descriptors: observation contracts, semantic classes, and build profiles.
2
3use serde::Serialize;
4
5use super::{
6    TransformDagEdgeV0, TransformPassKind, all_transform_pass_kinds, default_transform_dag_edges,
7    transform_pass_execution_phase,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
11#[serde(rename_all = "camelCase")]
12pub enum ObservationKindV0 {
13    SelectorMatching,
14    CascadeWinner,
15    CascadeWinnerEquality,
16    ExportedClassNames,
17    CustomPropertyComputedValue,
18    KeyframesReachability,
19    SourceMapTrace,
20    LayerRank,
21    Specificity,
22    Inheritance,
23    DeclarationOrder,
24    TargetPredicate,
25    ModuleResolution,
26    ImportContext,
27    ValueGraphReachability,
28    SemanticMarker,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
32#[serde(rename_all = "camelCase")]
33pub enum PassAssumptionKindV0 {
34    TokenBoundary,
35    SourceMapProvenance,
36    EquivalentLiteralValue,
37    SelectorSpecificity,
38    LonghandShorthandEquivalence,
39    DeclarationOrder,
40    TargetEnvironment,
41    Directionality,
42    NestedSelectorExpansion,
43    ScopedMatching,
44    LayerOrder,
45    StaticPredicate,
46    ImportWrapperProvenance,
47    ModuleNamespace,
48    SelectorIdentityMap,
49    ValueGraph,
50    CustomPropertyFixedPoint,
51    ClosedWorldReachability,
52    EmissionTrace,
53    SemanticMarkerRetention,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
57#[serde(rename_all = "camelCase")]
58pub struct PassSemanticContractV0 {
59    pub observes: Vec<ObservationKindV0>,
60    pub preserves: Vec<ObservationKindV0>,
61    pub requires: Vec<PassAssumptionKindV0>,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
65#[serde(rename_all = "camelCase")]
66pub enum PassObservationSurfaceV0 {
67    Declared(PassSemanticContractV0),
68    UnknownGap { reason: &'static str },
69}
70
71impl PassObservationSurfaceV0 {
72    pub fn is_declared(&self) -> bool {
73        matches!(self, Self::Declared(_))
74    }
75
76    pub fn gap_reason(&self) -> Option<&'static str> {
77        match self {
78            Self::Declared(_) => None,
79            Self::UnknownGap { reason } => Some(reason),
80        }
81    }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
85#[serde(rename_all = "camelCase")]
86pub struct TransformPassObservationRecordV0 {
87    pub schema_version: &'static str,
88    pub product: &'static str,
89    pub id: &'static str,
90    pub kind: TransformPassKind,
91    pub surface: PassObservationSurfaceV0,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
95#[serde(rename_all = "camelCase")]
96pub enum TransformPassClassV0 {
97    Structural,
98    TextLocal,
99    ModuleEvaluation,
100    Emission,
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
104#[serde(rename_all = "camelCase")]
105pub struct TransformPassDescriptorV0 {
106    pub schema_version: &'static str,
107    pub product: &'static str,
108    pub id: &'static str,
109    pub kind: TransformPassKind,
110    pub pass_class: TransformPassClassV0,
111    pub phase: u8,
112    pub phase_order: u16,
113    pub depends_on: Vec<&'static str>,
114    pub conflicts_with: Vec<&'static str>,
115}
116
117impl TransformPassDescriptorV0 {
118    pub fn is_structural(&self) -> bool {
119        self.pass_class == TransformPassClassV0::Structural
120    }
121
122    pub fn keeps_text_local_slice_rewrite(&self) -> bool {
123        self.pass_class == TransformPassClassV0::TextLocal
124    }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
128#[serde(rename_all = "camelCase")]
129pub struct TransformBuildProfileV0 {
130    pub schema_version: &'static str,
131    pub product: &'static str,
132    pub profile_id: &'static str,
133    pub pass_ids: Vec<&'static str>,
134}
135
136pub const STRICT_VERIFICATION_BUILD_PROFILE_ID_V0: &str = "strict-verification";
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
139#[serde(rename_all = "camelCase")]
140pub struct TransformStrictPolicyDescriptorV0 {
141    pub schema_version: &'static str,
142    pub product: &'static str,
143    pub profile_id: &'static str,
144    pub refuse_unknown_pass_ids: bool,
145    pub require_closed_world_evidence: bool,
146    pub require_complete_decisions: bool,
147    pub enforce_winner_equality: bool,
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
151#[serde(rename_all = "camelCase")]
152pub enum MinifyPassProfileClassV0 {
153    Safe,
154    Semantic,
155    ClosedWorldOnly,
156    Excluded,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
160#[serde(rename_all = "camelCase")]
161pub enum MinifyPassClassificationDerivationV0 {
162    ClosedWorldRequirement,
163    Policy,
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
167#[serde(rename_all = "camelCase")]
168pub struct MinifyPassClassificationV0 {
169    pub pass_id: &'static str,
170    pub kind: TransformPassKind,
171    pub profile_class: MinifyPassProfileClassV0,
172    pub derivation: MinifyPassClassificationDerivationV0,
173    pub reason: &'static str,
174}
175
176pub const fn transform_pass_requires_closed_world_bundle(kind: TransformPassKind) -> bool {
177    matches!(
178        kind,
179        TransformPassKind::LayerFlatten
180            | TransformPassKind::TreeShakeClass
181            | TransformPassKind::TreeShakeKeyframes
182            | TransformPassKind::TreeShakeValue
183            | TransformPassKind::TreeShakeCustomProperty
184    )
185}
186
187pub const fn minify_pass_profile_classification(
188    kind: TransformPassKind,
189) -> MinifyPassClassificationV0 {
190    let (profile_class, derivation, reason) = match kind {
191        TransformPassKind::WhitespaceStrip
192        | TransformPassKind::CommentStrip
193        | TransformPassKind::NumberCompression
194        | TransformPassKind::UnitNormalization
195        | TransformPassKind::ColorCompression
196        | TransformPassKind::UrlQuoteStrip
197        | TransformPassKind::StringQuoteNormalize
198        | TransformPassKind::CalcReduction
199        | TransformPassKind::PrintCss => (
200            MinifyPassProfileClassV0::Safe,
201            MinifyPassClassificationDerivationV0::Policy,
202            "local representation rewrite with no workspace reachability requirement",
203        ),
204        TransformPassKind::SelectorIsWhereCompression
205        | TransformPassKind::ShorthandCombining
206        | TransformPassKind::RuleDeduplication
207        | TransformPassKind::RuleMerging
208        | TransformPassKind::SelectorMerging
209        | TransformPassKind::EmptyRuleRemoval
210        | TransformPassKind::StalePrefixRemoval
211        | TransformPassKind::ValueResolution
212        | TransformPassKind::StaticVarSubstitution => (
213            MinifyPassProfileClassV0::Semantic,
214            MinifyPassClassificationDerivationV0::Policy,
215            "requires cascade, value, or semantic evidence beyond a local representation rewrite",
216        ),
217        TransformPassKind::LayerFlatten
218        | TransformPassKind::TreeShakeClass
219        | TransformPassKind::TreeShakeKeyframes
220        | TransformPassKind::TreeShakeValue
221        | TransformPassKind::TreeShakeCustomProperty => (
222            MinifyPassProfileClassV0::ClosedWorldOnly,
223            MinifyPassClassificationDerivationV0::ClosedWorldRequirement,
224            "requires a closed-world bundle before mutation",
225        ),
226        TransformPassKind::VendorPrefixing
227        | TransformPassKind::LightDarkLowering
228        | TransformPassKind::ColorMixLowering
229        | TransformPassKind::OklchOklabLowering
230        | TransformPassKind::ColorFunctionLowering
231        | TransformPassKind::RelativeColorLowering
232        | TransformPassKind::LogicalToPhysical
233        | TransformPassKind::NestingUnwrap
234        | TransformPassKind::ScopeFlatten
235        | TransformPassKind::SupportsStaticEval
236        | TransformPassKind::MediaStaticEval
237        | TransformPassKind::ContainerStaticEval
238        | TransformPassKind::NativeCssStaticEval
239        | TransformPassKind::ImportInline
240        | TransformPassKind::ScssModuleEvaluate
241        | TransformPassKind::LessModuleEvaluate
242        | TransformPassKind::HashCssModuleClassNames
243        | TransformPassKind::ResolveCssModulesComposes
244        | TransformPassKind::DeadMediaBranchRemoval
245        | TransformPassKind::DeadSupportsBranchRemoval
246        | TransformPassKind::DesignTokenRouting => (
247            MinifyPassProfileClassV0::Excluded,
248            MinifyPassClassificationDerivationV0::Policy,
249            "belongs to target lowering, module evaluation, identity rewriting, or another product surface",
250        ),
251    };
252
253    MinifyPassClassificationV0 {
254        pass_id: kind.id(),
255        kind,
256        profile_class,
257        derivation,
258        reason,
259    }
260}
261
262pub fn default_minify_pass_classifications() -> Vec<MinifyPassClassificationV0> {
263    all_transform_pass_kinds()
264        .into_iter()
265        .map(minify_pass_profile_classification)
266        .collect()
267}
268
269pub fn safe_minify_build_profile() -> TransformBuildProfileV0 {
270    minify_build_profile("safe", MinifyPassProfileClassV0::Safe)
271}
272
273pub fn semantic_minify_build_profile() -> TransformBuildProfileV0 {
274    minify_build_profile("semantic", MinifyPassProfileClassV0::Semantic)
275}
276
277pub fn closed_world_minify_build_profile() -> TransformBuildProfileV0 {
278    minify_build_profile("closed-world", MinifyPassProfileClassV0::ClosedWorldOnly)
279}
280
281pub fn default_minify_build_profiles() -> [TransformBuildProfileV0; 3] {
282    [
283        safe_minify_build_profile(),
284        semantic_minify_build_profile(),
285        closed_world_minify_build_profile(),
286    ]
287}
288
289pub fn strict_verification_build_profile() -> TransformBuildProfileV0 {
290    let profile_classes = [
291        MinifyPassProfileClassV0::Semantic,
292        MinifyPassProfileClassV0::ClosedWorldOnly,
293        MinifyPassProfileClassV0::Excluded,
294    ];
295    let mut passes = profile_classes
296        .into_iter()
297        .flat_map(|profile_class| {
298            default_minify_pass_classifications()
299                .into_iter()
300                .filter(move |entry| entry.profile_class == profile_class)
301        })
302        .filter(|entry| pass_requires_strict_winner_verification(entry.kind))
303        .map(|entry| entry.kind)
304        .collect::<Vec<_>>();
305    passes.sort_by_key(|pass| pass.ordinal());
306
307    transform_build_profile_from_passes(STRICT_VERIFICATION_BUILD_PROFILE_ID_V0, &passes)
308}
309
310pub fn strict_verification_policy_descriptor() -> TransformStrictPolicyDescriptorV0 {
311    TransformStrictPolicyDescriptorV0 {
312        schema_version: "0",
313        product: "omena-transform-cst.strict-policy",
314        profile_id: STRICT_VERIFICATION_BUILD_PROFILE_ID_V0,
315        refuse_unknown_pass_ids: true,
316        require_closed_world_evidence: true,
317        require_complete_decisions: true,
318        enforce_winner_equality: true,
319    }
320}
321
322pub fn strict_policy_descriptor_for_profile(
323    profile_id: &str,
324) -> Option<TransformStrictPolicyDescriptorV0> {
325    (profile_id == STRICT_VERIFICATION_BUILD_PROFILE_ID_V0)
326        .then(strict_verification_policy_descriptor)
327}
328
329fn pass_requires_strict_winner_verification(kind: TransformPassKind) -> bool {
330    let PassObservationSurfaceV0::Declared(contract) = pass_observation_contract(kind) else {
331        return false;
332    };
333    contract
334        .preserves
335        .contains(&ObservationKindV0::CascadeWinnerEquality)
336        && !contract
337            .preserves
338            .contains(&ObservationKindV0::SemanticMarker)
339        && !contract
340            .requires
341            .contains(&PassAssumptionKindV0::ClosedWorldReachability)
342}
343
344fn minify_build_profile(
345    profile_id: &'static str,
346    maximum_class: MinifyPassProfileClassV0,
347) -> TransformBuildProfileV0 {
348    let pass_ids = default_minify_pass_classifications()
349        .into_iter()
350        .filter(|entry| {
351            entry.profile_class != MinifyPassProfileClassV0::Excluded
352                && entry.profile_class <= maximum_class
353        })
354        .map(|entry| entry.pass_id)
355        .collect();
356    TransformBuildProfileV0 {
357        schema_version: "0",
358        product: "omena-transform-cst.build-profile",
359        profile_id,
360        pass_ids,
361    }
362}
363
364pub fn transform_build_profile_from_passes(
365    profile_id: &'static str,
366    passes: &[TransformPassKind],
367) -> TransformBuildProfileV0 {
368    TransformBuildProfileV0 {
369        schema_version: "0",
370        product: "omena-transform-cst.build-profile",
371        profile_id,
372        pass_ids: passes.iter().map(|pass| pass.id()).collect(),
373    }
374}
375
376pub fn default_transform_pass_descriptors() -> Vec<TransformPassDescriptorV0> {
377    let dag_edges = default_transform_dag_edges();
378    all_transform_pass_kinds()
379        .into_iter()
380        .map(|kind| transform_pass_descriptor_with_edges(kind, dag_edges.as_slice()))
381        .collect()
382}
383
384pub fn transform_pass_descriptor(kind: TransformPassKind) -> TransformPassDescriptorV0 {
385    let dag_edges = default_transform_dag_edges();
386    transform_pass_descriptor_with_edges(kind, dag_edges.as_slice())
387}
388
389pub fn default_transform_pass_observation_records() -> Vec<TransformPassObservationRecordV0> {
390    all_transform_pass_kinds()
391        .into_iter()
392        .map(|kind| TransformPassObservationRecordV0 {
393            schema_version: "0",
394            product: "omena-transform-cst.pass-observation-surface",
395            id: kind.id(),
396            kind,
397            surface: pass_observation_contract(kind),
398        })
399        .collect()
400}
401
402pub fn pass_observation_contract(kind: TransformPassKind) -> PassObservationSurfaceV0 {
403    match kind {
404        TransformPassKind::WhitespaceStrip => declared_observation_contract(
405            &[ObservationKindV0::SourceMapTrace],
406            &[
407                ObservationKindV0::SelectorMatching,
408                ObservationKindV0::CascadeWinner,
409                ObservationKindV0::DeclarationOrder,
410                ObservationKindV0::SourceMapTrace,
411            ],
412            &[PassAssumptionKindV0::TokenBoundary],
413        ),
414        TransformPassKind::CommentStrip => declared_observation_contract(
415            &[ObservationKindV0::SourceMapTrace],
416            &[
417                ObservationKindV0::SelectorMatching,
418                ObservationKindV0::CascadeWinner,
419                ObservationKindV0::DeclarationOrder,
420                ObservationKindV0::SourceMapTrace,
421            ],
422            &[PassAssumptionKindV0::SourceMapProvenance],
423        ),
424        TransformPassKind::NumberCompression
425        | TransformPassKind::UnitNormalization
426        | TransformPassKind::ColorCompression
427        | TransformPassKind::UrlQuoteStrip
428        | TransformPassKind::StringQuoteNormalize => declared_observation_contract(
429            &[
430                ObservationKindV0::CascadeWinner,
431                ObservationKindV0::CustomPropertyComputedValue,
432                ObservationKindV0::SourceMapTrace,
433            ],
434            &[
435                ObservationKindV0::CascadeWinner,
436                ObservationKindV0::CustomPropertyComputedValue,
437                ObservationKindV0::SourceMapTrace,
438            ],
439            &[PassAssumptionKindV0::EquivalentLiteralValue],
440        ),
441        TransformPassKind::SelectorIsWhereCompression => declared_observation_contract(
442            &[
443                ObservationKindV0::SelectorMatching,
444                ObservationKindV0::Specificity,
445                ObservationKindV0::KeyframesReachability,
446            ],
447            &[
448                ObservationKindV0::SelectorMatching,
449                ObservationKindV0::Specificity,
450                ObservationKindV0::KeyframesReachability,
451            ],
452            &[PassAssumptionKindV0::SelectorSpecificity],
453        ),
454        TransformPassKind::ShorthandCombining => declared_observation_contract(
455            &[
456                ObservationKindV0::CascadeWinner,
457                ObservationKindV0::DeclarationOrder,
458                ObservationKindV0::Inheritance,
459            ],
460            &[
461                ObservationKindV0::CascadeWinner,
462                ObservationKindV0::DeclarationOrder,
463                ObservationKindV0::Inheritance,
464            ],
465            &[PassAssumptionKindV0::LonghandShorthandEquivalence],
466        ),
467        TransformPassKind::RuleDeduplication | TransformPassKind::RuleMerging => {
468            declared_observation_contract(
469                &[
470                    ObservationKindV0::CascadeWinner,
471                    ObservationKindV0::CascadeWinnerEquality,
472                    ObservationKindV0::LayerRank,
473                    ObservationKindV0::Specificity,
474                    ObservationKindV0::DeclarationOrder,
475                ],
476                &[
477                    ObservationKindV0::CascadeWinner,
478                    ObservationKindV0::CascadeWinnerEquality,
479                    ObservationKindV0::LayerRank,
480                    ObservationKindV0::Specificity,
481                    ObservationKindV0::DeclarationOrder,
482                ],
483                &[PassAssumptionKindV0::DeclarationOrder],
484            )
485        }
486        TransformPassKind::SelectorMerging => declared_observation_contract(
487            &[
488                ObservationKindV0::CascadeWinnerEquality,
489                ObservationKindV0::SelectorMatching,
490                ObservationKindV0::Specificity,
491                ObservationKindV0::ExportedClassNames,
492            ],
493            &[
494                ObservationKindV0::CascadeWinnerEquality,
495                ObservationKindV0::SelectorMatching,
496                ObservationKindV0::Specificity,
497                ObservationKindV0::ExportedClassNames,
498            ],
499            &[
500                PassAssumptionKindV0::SelectorSpecificity,
501                PassAssumptionKindV0::SelectorIdentityMap,
502            ],
503        ),
504        TransformPassKind::EmptyRuleRemoval => declared_observation_contract(
505            &[
506                ObservationKindV0::CascadeWinnerEquality,
507                ObservationKindV0::SemanticMarker,
508                ObservationKindV0::SourceMapTrace,
509            ],
510            &[
511                ObservationKindV0::CascadeWinnerEquality,
512                ObservationKindV0::SemanticMarker,
513                ObservationKindV0::SourceMapTrace,
514            ],
515            &[PassAssumptionKindV0::SemanticMarkerRetention],
516        ),
517        TransformPassKind::VendorPrefixing
518        | TransformPassKind::StalePrefixRemoval
519        | TransformPassKind::LightDarkLowering
520        | TransformPassKind::ColorMixLowering
521        | TransformPassKind::OklchOklabLowering
522        | TransformPassKind::ColorFunctionLowering
523        | TransformPassKind::RelativeColorLowering => declared_observation_contract(
524            &[
525                ObservationKindV0::TargetPredicate,
526                ObservationKindV0::CascadeWinner,
527                ObservationKindV0::CustomPropertyComputedValue,
528            ],
529            &[
530                ObservationKindV0::CascadeWinner,
531                ObservationKindV0::CustomPropertyComputedValue,
532            ],
533            &[PassAssumptionKindV0::TargetEnvironment],
534        ),
535        TransformPassKind::LogicalToPhysical => declared_observation_contract(
536            &[
537                ObservationKindV0::CascadeWinner,
538                ObservationKindV0::Inheritance,
539            ],
540            &[
541                ObservationKindV0::CascadeWinner,
542                ObservationKindV0::Inheritance,
543            ],
544            &[PassAssumptionKindV0::Directionality],
545        ),
546        TransformPassKind::NestingUnwrap => declared_observation_contract(
547            &[
548                ObservationKindV0::CascadeWinnerEquality,
549                ObservationKindV0::SelectorMatching,
550                ObservationKindV0::Specificity,
551            ],
552            &[
553                ObservationKindV0::CascadeWinnerEquality,
554                ObservationKindV0::SelectorMatching,
555                ObservationKindV0::Specificity,
556            ],
557            &[PassAssumptionKindV0::NestedSelectorExpansion],
558        ),
559        TransformPassKind::ScopeFlatten => declared_observation_contract(
560            &[
561                ObservationKindV0::CascadeWinnerEquality,
562                ObservationKindV0::SelectorMatching,
563            ],
564            &[
565                ObservationKindV0::CascadeWinnerEquality,
566                ObservationKindV0::SelectorMatching,
567            ],
568            &[PassAssumptionKindV0::ScopedMatching],
569        ),
570        TransformPassKind::LayerFlatten => declared_observation_contract(
571            &[
572                ObservationKindV0::LayerRank,
573                ObservationKindV0::CascadeWinner,
574                ObservationKindV0::CascadeWinnerEquality,
575            ],
576            &[
577                ObservationKindV0::LayerRank,
578                ObservationKindV0::CascadeWinner,
579                ObservationKindV0::CascadeWinnerEquality,
580            ],
581            &[PassAssumptionKindV0::LayerOrder],
582        ),
583        TransformPassKind::SupportsStaticEval
584        | TransformPassKind::MediaStaticEval
585        | TransformPassKind::ContainerStaticEval
586        | TransformPassKind::NativeCssStaticEval => declared_observation_contract(
587            &[
588                ObservationKindV0::TargetPredicate,
589                ObservationKindV0::CascadeWinner,
590            ],
591            &[ObservationKindV0::CascadeWinner],
592            &[PassAssumptionKindV0::StaticPredicate],
593        ),
594        TransformPassKind::CalcReduction => declared_observation_contract(
595            &[
596                ObservationKindV0::CustomPropertyComputedValue,
597                ObservationKindV0::CascadeWinner,
598            ],
599            &[
600                ObservationKindV0::CustomPropertyComputedValue,
601                ObservationKindV0::CascadeWinner,
602            ],
603            &[PassAssumptionKindV0::EquivalentLiteralValue],
604        ),
605        TransformPassKind::ImportInline => declared_observation_contract(
606            &[
607                ObservationKindV0::ImportContext,
608                ObservationKindV0::LayerRank,
609                ObservationKindV0::CascadeWinner,
610                ObservationKindV0::SourceMapTrace,
611            ],
612            &[
613                ObservationKindV0::ImportContext,
614                ObservationKindV0::LayerRank,
615                ObservationKindV0::CascadeWinner,
616                ObservationKindV0::SourceMapTrace,
617            ],
618            &[PassAssumptionKindV0::ImportWrapperProvenance],
619        ),
620        TransformPassKind::ScssModuleEvaluate | TransformPassKind::LessModuleEvaluate => {
621            declared_observation_contract(
622                &[
623                    ObservationKindV0::ModuleResolution,
624                    ObservationKindV0::CustomPropertyComputedValue,
625                    ObservationKindV0::SourceMapTrace,
626                ],
627                &[
628                    ObservationKindV0::ModuleResolution,
629                    ObservationKindV0::CustomPropertyComputedValue,
630                    ObservationKindV0::SourceMapTrace,
631                ],
632                &[PassAssumptionKindV0::ModuleNamespace],
633            )
634        }
635        TransformPassKind::HashCssModuleClassNames => declared_observation_contract(
636            &[
637                ObservationKindV0::ExportedClassNames,
638                ObservationKindV0::SelectorMatching,
639            ],
640            &[
641                ObservationKindV0::ExportedClassNames,
642                ObservationKindV0::SelectorMatching,
643            ],
644            &[PassAssumptionKindV0::SelectorIdentityMap],
645        ),
646        TransformPassKind::ResolveCssModulesComposes => declared_observation_contract(
647            &[
648                ObservationKindV0::ExportedClassNames,
649                ObservationKindV0::ModuleResolution,
650            ],
651            &[
652                ObservationKindV0::ExportedClassNames,
653                ObservationKindV0::ModuleResolution,
654            ],
655            &[PassAssumptionKindV0::SelectorIdentityMap],
656        ),
657        TransformPassKind::ValueResolution => declared_observation_contract(
658            &[ObservationKindV0::ValueGraphReachability],
659            &[ObservationKindV0::ValueGraphReachability],
660            &[PassAssumptionKindV0::ValueGraph],
661        ),
662        TransformPassKind::StaticVarSubstitution => declared_observation_contract(
663            &[ObservationKindV0::CustomPropertyComputedValue],
664            &[ObservationKindV0::CustomPropertyComputedValue],
665            &[PassAssumptionKindV0::CustomPropertyFixedPoint],
666        ),
667        TransformPassKind::TreeShakeClass => declared_observation_contract(
668            &[
669                ObservationKindV0::CascadeWinnerEquality,
670                ObservationKindV0::ExportedClassNames,
671                ObservationKindV0::SelectorMatching,
672            ],
673            &[
674                ObservationKindV0::CascadeWinnerEquality,
675                ObservationKindV0::ExportedClassNames,
676                ObservationKindV0::SelectorMatching,
677            ],
678            &[PassAssumptionKindV0::ClosedWorldReachability],
679        ),
680        TransformPassKind::TreeShakeKeyframes => declared_observation_contract(
681            &[
682                ObservationKindV0::CascadeWinnerEquality,
683                ObservationKindV0::KeyframesReachability,
684            ],
685            &[
686                ObservationKindV0::CascadeWinnerEquality,
687                ObservationKindV0::KeyframesReachability,
688            ],
689            &[PassAssumptionKindV0::ClosedWorldReachability],
690        ),
691        TransformPassKind::TreeShakeValue => declared_observation_contract(
692            &[
693                ObservationKindV0::CascadeWinnerEquality,
694                ObservationKindV0::ValueGraphReachability,
695            ],
696            &[
697                ObservationKindV0::CascadeWinnerEquality,
698                ObservationKindV0::ValueGraphReachability,
699            ],
700            &[PassAssumptionKindV0::ClosedWorldReachability],
701        ),
702        TransformPassKind::TreeShakeCustomProperty => declared_observation_contract(
703            &[
704                ObservationKindV0::CascadeWinnerEquality,
705                ObservationKindV0::CustomPropertyComputedValue,
706                ObservationKindV0::ValueGraphReachability,
707            ],
708            &[
709                ObservationKindV0::CascadeWinnerEquality,
710                ObservationKindV0::CustomPropertyComputedValue,
711                ObservationKindV0::ValueGraphReachability,
712            ],
713            &[PassAssumptionKindV0::ClosedWorldReachability],
714        ),
715        TransformPassKind::DeadMediaBranchRemoval
716        | TransformPassKind::DeadSupportsBranchRemoval => declared_observation_contract(
717            &[
718                ObservationKindV0::TargetPredicate,
719                ObservationKindV0::CascadeWinner,
720            ],
721            &[ObservationKindV0::CascadeWinner],
722            &[PassAssumptionKindV0::StaticPredicate],
723        ),
724        TransformPassKind::DesignTokenRouting => declared_observation_contract(
725            &[
726                ObservationKindV0::CustomPropertyComputedValue,
727                ObservationKindV0::ModuleResolution,
728            ],
729            &[
730                ObservationKindV0::CustomPropertyComputedValue,
731                ObservationKindV0::ModuleResolution,
732            ],
733            &[PassAssumptionKindV0::ModuleNamespace],
734        ),
735        TransformPassKind::PrintCss => declared_observation_contract(
736            &[ObservationKindV0::SourceMapTrace],
737            &[ObservationKindV0::SourceMapTrace],
738            &[PassAssumptionKindV0::EmissionTrace],
739        ),
740    }
741}
742
743fn declared_observation_contract(
744    observes: &[ObservationKindV0],
745    preserves: &[ObservationKindV0],
746    requires: &[PassAssumptionKindV0],
747) -> PassObservationSurfaceV0 {
748    PassObservationSurfaceV0::Declared(PassSemanticContractV0 {
749        observes: observes.to_vec(),
750        preserves: preserves.to_vec(),
751        requires: requires.to_vec(),
752    })
753}
754
755pub const fn transform_pass_class(kind: TransformPassKind) -> TransformPassClassV0 {
756    match kind {
757        TransformPassKind::NestingUnwrap
758        | TransformPassKind::ScopeFlatten
759        | TransformPassKind::LayerFlatten
760        | TransformPassKind::RuleDeduplication
761        | TransformPassKind::RuleMerging
762        | TransformPassKind::SelectorMerging
763        | TransformPassKind::EmptyRuleRemoval
764        | TransformPassKind::SupportsStaticEval
765        | TransformPassKind::MediaStaticEval
766        | TransformPassKind::ContainerStaticEval
767        | TransformPassKind::NativeCssStaticEval
768        | TransformPassKind::DeadMediaBranchRemoval
769        | TransformPassKind::DeadSupportsBranchRemoval
770        | TransformPassKind::TreeShakeClass
771        | TransformPassKind::TreeShakeKeyframes
772        | TransformPassKind::TreeShakeValue
773        | TransformPassKind::TreeShakeCustomProperty
774        | TransformPassKind::ResolveCssModulesComposes
775        | TransformPassKind::HashCssModuleClassNames
776        | TransformPassKind::ImportInline
777        | TransformPassKind::DesignTokenRouting => TransformPassClassV0::Structural,
778        TransformPassKind::ScssModuleEvaluate | TransformPassKind::LessModuleEvaluate => {
779            TransformPassClassV0::ModuleEvaluation
780        }
781        TransformPassKind::PrintCss => TransformPassClassV0::Emission,
782        _ => TransformPassClassV0::TextLocal,
783    }
784}
785
786const fn transform_pass_phase_order(kind: TransformPassKind) -> u16 {
787    match kind {
788        TransformPassKind::WhitespaceStrip => 10,
789        TransformPassKind::CommentStrip => 20,
790        TransformPassKind::NumberCompression => 30,
791        TransformPassKind::UnitNormalization => 40,
792        TransformPassKind::ColorCompression => 50,
793        TransformPassKind::UrlQuoteStrip => 60,
794        TransformPassKind::StringQuoteNormalize => 70,
795        TransformPassKind::SelectorIsWhereCompression => 80,
796        TransformPassKind::ShorthandCombining => 90,
797        TransformPassKind::RuleDeduplication => 100,
798        TransformPassKind::RuleMerging => 110,
799        TransformPassKind::SelectorMerging => 120,
800        TransformPassKind::EmptyRuleRemoval => 130,
801        TransformPassKind::VendorPrefixing => 140,
802        TransformPassKind::StalePrefixRemoval => 150,
803        TransformPassKind::LightDarkLowering => 160,
804        TransformPassKind::ColorMixLowering => 170,
805        TransformPassKind::OklchOklabLowering => 180,
806        TransformPassKind::ColorFunctionLowering => 190,
807        TransformPassKind::LogicalToPhysical => 200,
808        TransformPassKind::NestingUnwrap => 210,
809        TransformPassKind::ScopeFlatten => 220,
810        TransformPassKind::LayerFlatten => 230,
811        TransformPassKind::SupportsStaticEval => 240,
812        TransformPassKind::MediaStaticEval => 250,
813        TransformPassKind::CalcReduction => 260,
814        TransformPassKind::ImportInline => 270,
815        TransformPassKind::ScssModuleEvaluate => 280,
816        TransformPassKind::LessModuleEvaluate => 290,
817        TransformPassKind::HashCssModuleClassNames => 300,
818        TransformPassKind::ResolveCssModulesComposes => 310,
819        TransformPassKind::ValueResolution => 320,
820        TransformPassKind::StaticVarSubstitution => 330,
821        TransformPassKind::TreeShakeClass => 340,
822        TransformPassKind::TreeShakeKeyframes => 350,
823        TransformPassKind::TreeShakeValue => 360,
824        TransformPassKind::TreeShakeCustomProperty => 370,
825        TransformPassKind::DeadMediaBranchRemoval => 380,
826        TransformPassKind::DeadSupportsBranchRemoval => 390,
827        TransformPassKind::DesignTokenRouting => 400,
828        TransformPassKind::PrintCss => 410,
829        TransformPassKind::RelativeColorLowering => 420,
830        TransformPassKind::ContainerStaticEval => 430,
831        TransformPassKind::NativeCssStaticEval => 440,
832    }
833}
834
835fn transform_pass_descriptor_with_edges(
836    kind: TransformPassKind,
837    dag_edges: &[TransformDagEdgeV0],
838) -> TransformPassDescriptorV0 {
839    TransformPassDescriptorV0 {
840        schema_version: "0",
841        product: "omena-transform-cst.pass-descriptor",
842        id: kind.id(),
843        kind,
844        pass_class: transform_pass_class(kind),
845        phase: transform_pass_execution_phase(kind),
846        phase_order: transform_pass_phase_order(kind),
847        depends_on: dag_edges
848            .iter()
849            .filter(|edge| edge.to == kind.id())
850            .map(|edge| edge.from)
851            .collect(),
852        conflicts_with: transform_pass_conflicts_with(kind, dag_edges),
853    }
854}
855
856fn transform_pass_conflicts_with(
857    kind: TransformPassKind,
858    dag_edges: &[TransformDagEdgeV0],
859) -> Vec<&'static str> {
860    let Some(conflict_family) = transform_pass_conflict_family(kind) else {
861        return Vec::new();
862    };
863    all_transform_pass_kinds()
864        .into_iter()
865        .filter(|other| *other != kind)
866        .filter(|other| transform_pass_conflict_family(*other) == Some(conflict_family))
867        .filter(|other| {
868            transform_pass_execution_phase(*other) == transform_pass_execution_phase(kind)
869        })
870        .filter(|other| !dag_path_exists(kind.id(), other.id(), dag_edges))
871        .filter(|other| !dag_path_exists(other.id(), kind.id(), dag_edges))
872        .map(|other| other.id())
873        .collect()
874}
875
876fn transform_pass_conflict_family(kind: TransformPassKind) -> Option<&'static str> {
877    match kind {
878        TransformPassKind::ColorMixLowering | TransformPassKind::ColorFunctionLowering => {
879            Some("nested-color-function-lowering")
880        }
881        _ => None,
882    }
883}
884
885fn dag_path_exists(from: &'static str, to: &'static str, dag_edges: &[TransformDagEdgeV0]) -> bool {
886    let mut stack = vec![from];
887    let mut visited = Vec::new();
888    while let Some(current) = stack.pop() {
889        if current == to {
890            return true;
891        }
892        if visited.contains(&current) {
893            continue;
894        }
895        visited.push(current);
896        for edge in dag_edges.iter().filter(|edge| edge.from == current) {
897            stack.push(edge.to);
898        }
899    }
900    false
901}
902
903#[cfg(test)]
904mod tests {
905    use super::*;
906    use std::collections::BTreeSet;
907
908    #[test]
909    fn minify_manifest_classifies_the_complete_pass_catalog() {
910        let manifest = default_minify_pass_classifications();
911        let pass_ids = manifest
912            .iter()
913            .map(|entry| entry.pass_id)
914            .collect::<BTreeSet<_>>();
915
916        assert_eq!(manifest.len(), crate::TRANSFORM_PASS_CATALOG_LEN);
917        assert_eq!(pass_ids.len(), crate::TRANSFORM_PASS_CATALOG_LEN);
918        assert!(manifest.iter().all(|entry| !entry.reason.is_empty()));
919    }
920
921    #[test]
922    fn minify_closed_world_classification_matches_the_runtime_gate() {
923        for entry in default_minify_pass_classifications() {
924            let requires_closed_world = transform_pass_requires_closed_world_bundle(entry.kind);
925            let classified_closed_world =
926                entry.profile_class == MinifyPassProfileClassV0::ClosedWorldOnly;
927            assert_eq!(
928                classified_closed_world, requires_closed_world,
929                "{}",
930                entry.pass_id
931            );
932            if classified_closed_world {
933                assert_eq!(
934                    entry.derivation,
935                    MinifyPassClassificationDerivationV0::ClosedWorldRequirement
936                );
937            } else {
938                assert_eq!(
939                    entry.derivation,
940                    MinifyPassClassificationDerivationV0::Policy
941                );
942            }
943        }
944    }
945
946    #[test]
947    fn minify_profiles_are_monotone_and_exclude_non_minify_passes() {
948        let [safe, semantic, closed_world] = default_minify_build_profiles();
949        let safe_passes = safe.pass_ids.iter().copied().collect::<BTreeSet<_>>();
950        let semantic_passes = semantic.pass_ids.iter().copied().collect::<BTreeSet<_>>();
951        let closed_world_passes = closed_world
952            .pass_ids
953            .iter()
954            .copied()
955            .collect::<BTreeSet<_>>();
956
957        assert_eq!(safe.profile_id, "safe");
958        assert_eq!(semantic.profile_id, "semantic");
959        assert_eq!(closed_world.profile_id, "closed-world");
960        assert!(safe_passes.is_subset(&semantic_passes));
961        assert!(semantic_passes.is_subset(&closed_world_passes));
962        assert!(safe_passes.len() < semantic_passes.len());
963        assert!(semantic_passes.len() < closed_world_passes.len());
964
965        for entry in default_minify_pass_classifications()
966            .into_iter()
967            .filter(|entry| entry.profile_class == MinifyPassProfileClassV0::Excluded)
968        {
969            assert!(!closed_world_passes.contains(entry.pass_id));
970        }
971    }
972
973    #[test]
974    fn strict_profile_is_derived_from_bucket_observation_contracts() {
975        let profile = strict_verification_build_profile();
976        let policy = strict_verification_policy_descriptor();
977        let expected = [
978            TransformPassKind::RuleDeduplication,
979            TransformPassKind::RuleMerging,
980            TransformPassKind::SelectorMerging,
981            TransformPassKind::NestingUnwrap,
982            TransformPassKind::ScopeFlatten,
983            TransformPassKind::LayerFlatten,
984        ]
985        .into_iter()
986        .map(TransformPassKind::id)
987        .collect::<Vec<_>>();
988
989        assert_eq!(profile.profile_id, STRICT_VERIFICATION_BUILD_PROFILE_ID_V0);
990        assert_eq!(profile.pass_ids, expected);
991        assert!(
992            !profile
993                .pass_ids
994                .contains(&TransformPassKind::EmptyRuleRemoval.id())
995        );
996        assert_eq!(policy.profile_id, profile.profile_id);
997        assert!(policy.refuse_unknown_pass_ids);
998        assert!(policy.require_closed_world_evidence);
999        assert!(policy.require_complete_decisions);
1000        assert!(policy.enforce_winner_equality);
1001    }
1002}