Skip to main content

omena_transform_passes/
model.rs

1//! Public transform planning, execution, provenance, and context contracts.
2//!
3//! These data models are the stable JSON-facing boundary for Omena CSS transform
4//! passes. Runtime modules own mutation execution, while this module keeps the
5//! pass registry, execution summaries, semantic-removal witnesses, fuzz reports,
6//! and cross-file transform context shapes serializable for `omena-query`,
7//! bindings, CLI runners, and release gates.
8
9use omena_abstract_value::{AbstractCssValueV0, FactPrecision};
10use omena_cascade::{
11    CascadeDeclaration, CascadeLevel, CascadeOriginV0, CascadeOutcome, CascadeProof,
12    ElementSignature, GuardedCascadeWinnerAuthorityV0, GuardedCascadeWinnerPlaneAnswerV0,
13    GuardedCascadeWinnerRootV0, SupportsTargetCapabilityV0,
14};
15use omena_cascade_proof::{
16    CanonicalSmtInputV0, DischargeLedgerLookupStatusV0, DischargeLedgerLookupV0,
17    DischargeLedgerVerdictV0,
18};
19use omena_evidence_graph::{
20    EvidenceDemandEdgeV0, EvidenceGraphBuildErrorV0, EvidenceGraphV0, EvidenceNodeKeyV0,
21    EvidenceNodeSeedV0, GuaranteeFamilyV0, GuaranteeKindV0, build_evidence_graph_from_edges_v0,
22};
23use omena_incremental::{IncrementalComputationPlanV0, IncrementalSnapshotV0};
24use omena_parser::ModuleInstanceKeyV0;
25use omena_syntax::ident::AuthoredPropertyTextV0;
26use omena_transform_cst::{
27    StableNodeKeyV0, TransformBuildProfileV0, TransformDagEdgeV0, TransformPassContractV0,
28    TransformPassDescriptorV0, TransformPassKind, TransformStrictPolicyDescriptorV0,
29    strict_policy_descriptor_for_profile,
30};
31use serde::{Deserialize, Serialize};
32use serde_json::Value;
33
34const TRANSFORM_PASS_OUTCOME_EVIDENCE_QUERY_V0: &str =
35    "omena-transform-passes.transform-pass-execution-outcome";
36const TRANSFORM_PROVENANCE_NODE_EVIDENCE_QUERY_V0: &str =
37    "omena-transform-passes.provenance-derivation-node";
38const TRANSFORM_EVIDENCE_EDGE_KIND_V0: &str = "transform-evidence";
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
41#[serde(rename_all = "camelCase")]
42pub enum TransformPassExecutionStatus {
43    RegistryAndPlannerReady,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
47#[serde(rename_all = "camelCase")]
48pub enum TransformPassDispatchKindV0 {
49    TextLocalSliceRewrite,
50    StructuralIrTransaction,
51    ModuleEvaluationHandler,
52    EmissionBoundary,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
56#[serde(rename_all = "camelCase")]
57pub struct TransformPassRegistryEntryV0 {
58    pub contract: TransformPassContractV0,
59    pub descriptor: TransformPassDescriptorV0,
60    pub module_family: &'static str,
61    pub query_family: &'static str,
62    pub dispatch_kind: TransformPassDispatchKindV0,
63    pub execution_status: TransformPassExecutionStatus,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
67#[serde(rename_all = "camelCase")]
68pub struct TransformPassRegistryV0 {
69    pub schema_version: &'static str,
70    pub product: &'static str,
71    pub entries: Vec<TransformPassRegistryEntryV0>,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
75#[serde(rename_all = "camelCase")]
76pub struct TransformPassesBoundarySummaryV0 {
77    pub schema_version: &'static str,
78    pub product: &'static str,
79    pub registry_entries: Vec<TransformPassRegistryEntryV0>,
80    pub dag_edges: Vec<TransformDagEdgeV0>,
81    pub pass_count: usize,
82    pub full_catalog_registered: bool,
83    pub semantic_aware_pass_count: usize,
84    pub cascade_aware_pass_count: usize,
85    pub structural_pass_count: usize,
86    pub text_local_pass_count: usize,
87    pub module_evaluation_pass_count: usize,
88    pub planner_enforces_dag_edges: bool,
89    pub planner_uses_pass_descriptors: bool,
90    pub ordinal_has_execution_semantics: bool,
91    pub execution_runtime_ready: bool,
92    pub incremental_execution_runtime_ready: bool,
93    pub module_evaluation_native_output_marker: &'static str,
94    pub module_evaluation_requires_native_product_output: bool,
95    pub module_evaluation_requires_oracle_readiness: bool,
96    pub module_evaluation_legacy_output_is_oracle_only: bool,
97    pub module_evaluation_preserves_source_without_native_output: bool,
98    pub implemented_mutation_pass_ids: Vec<&'static str>,
99    pub next_surfaces: Vec<&'static str>,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
103#[serde(rename_all = "camelCase")]
104pub struct TransformPassPlanV0 {
105    pub schema_version: &'static str,
106    pub product: &'static str,
107    pub build_profile: TransformBuildProfileV0,
108    pub requested_pass_ids: Vec<&'static str>,
109    pub ordered_pass_ids: Vec<&'static str>,
110    pub satisfied_dag_edge_count: usize,
111    pub violated_dag_edge_count: usize,
112    pub all_requested_registered: bool,
113    pub conflicting_unordered_pass_pairs: Vec<TransformPlanPassConflictV0>,
114}
115
116#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
117#[serde(rename_all = "camelCase")]
118pub struct TransformPlanPassConflictV0 {
119    pub pass_a: &'static str,
120    pub pass_b: &'static str,
121}
122
123#[non_exhaustive]
124#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
125#[serde(rename_all = "camelCase")]
126/// One dependency edge in a transform-planner cycle witness.
127pub struct TransformPlanDependencyEdgeV0 {
128    pub prerequisite_pass_id: &'static str,
129    pub dependent_pass_id: &'static str,
130}
131
132#[non_exhaustive]
133#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
134#[serde(rename_all = "camelCase")]
135/// A closed dependency path that prevents the planner from selecting a next pass.
136pub struct TransformPlanDependencyCycleV0 {
137    /// Pass identifiers in traversal order, with the first identifier repeated at the end.
138    pub cycle_path: Vec<&'static str>,
139    pub dependency_edges: Vec<TransformPlanDependencyEdgeV0>,
140}
141
142#[non_exhaustive]
143#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
144#[serde(
145    tag = "kind",
146    rename_all = "camelCase",
147    rename_all_fields = "camelCase"
148)]
149/// Typed failures returned by checked transform-pass planning.
150pub enum TransformPassPlanningErrorV0 {
151    DependencyCycle {
152        cycle: TransformPlanDependencyCycleV0,
153    },
154    UnorderedPassConflict {
155        conflict: TransformPlanPassConflictV0,
156    },
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
160#[serde(rename_all = "camelCase")]
161pub struct TransformStructuralIrShadowFieldReportV0 {
162    pub field: &'static str,
163    pub string_path_values: Vec<String>,
164    pub ir_path_values: Vec<String>,
165    pub typed_path_values: Vec<String>,
166    pub matches: bool,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
170#[serde(rename_all = "camelCase")]
171pub struct TransformStructuralIrShadowFixtureReportV0 {
172    pub schema_version: &'static str,
173    pub product: &'static str,
174    pub fixture: String,
175    pub pass_id: &'static str,
176    pub dialect: &'static str,
177    pub string_path_mutation_count: Option<usize>,
178    pub ir_path_mutation_count: Option<usize>,
179    pub typed_path_mutation_count: Option<usize>,
180    pub ir_path_transaction_commit_count: Option<u64>,
181    pub typed_payload_projections_consumed: usize,
182    pub typed_payload_memo_hits: usize,
183    pub fields: Vec<TransformStructuralIrShadowFieldReportV0>,
184    pub all_fields_match: bool,
185    pub all_typed_path_fields_match: bool,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
189#[serde(rename_all = "camelCase")]
190pub struct TransformStructuralIrShadowEquivalenceReportV0 {
191    pub schema_version: &'static str,
192    pub product: &'static str,
193    pub fixture_count: usize,
194    pub compared_pass_ids: Vec<&'static str>,
195    pub compared_fields: Vec<&'static str>,
196    pub reports: Vec<TransformStructuralIrShadowFixtureReportV0>,
197    pub all_fields_match: bool,
198    pub all_typed_path_fields_match: bool,
199    pub typed_payload_projections_consumed: usize,
200    pub typed_payload_memo_hits: usize,
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
204#[serde(rename_all = "camelCase")]
205pub enum TransformPassRuntimeStatus {
206    Applied,
207    NoChange,
208    PlannedOnly,
209}
210
211#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
212#[serde(rename_all = "camelCase")]
213pub struct TransformPassExecutionOutcomeV0 {
214    pub pass_id: &'static str,
215    pub status: TransformPassRuntimeStatus,
216    pub input_byte_len: usize,
217    pub output_byte_len: usize,
218    pub mutation_count: usize,
219    pub provenance_preserved: bool,
220    pub detail: &'static str,
221}
222
223impl TransformPassExecutionOutcomeV0 {
224    pub fn evidence_node_key(&self) -> EvidenceNodeKeyV0 {
225        EvidenceNodeKeyV0::new(TRANSFORM_PASS_OUTCOME_EVIDENCE_QUERY_V0, self.pass_id)
226    }
227
228    pub fn evidence_node_seed(&self) -> EvidenceNodeSeedV0 {
229        EvidenceNodeSeedV0::new(
230            self.evidence_node_key(),
231            vec![
232                ["pass:", self.pass_id].concat(),
233                ["detail:", self.detail].concat(),
234                ["mutationCount:", self.mutation_count.to_string().as_str()].concat(),
235                [
236                    "provenancePreserved:",
237                    self.provenance_preserved.to_string().as_str(),
238                ]
239                .concat(),
240            ],
241            GuaranteeKindV0::for_label_less_family(),
242        )
243    }
244
245    pub fn evidence_demand_edge(&self) -> EvidenceDemandEdgeV0 {
246        EvidenceDemandEdgeV0::new(
247            TRANSFORM_PASS_OUTCOME_EVIDENCE_QUERY_V0,
248            self.evidence_node_key(),
249            TRANSFORM_EVIDENCE_EDGE_KIND_V0,
250        )
251    }
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
255#[serde(rename_all = "camelCase")]
256pub enum TransformEvaluationProfileV0 {
257    Scss,
258    Less,
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
262#[serde(tag = "kind", rename_all = "camelCase")]
263pub enum TransformPreconditionV0 {
264    EvaluatorOutput {
265        profile: TransformEvaluationProfileV0,
266    },
267    ResolvedImportReplacements,
268    CssModulesComposesResolution,
269    DesignTokenRoutes,
270    SelectorIdentity,
271    ClosedStyleWorldBundle,
272    ClosedWorldBundle,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
276#[serde(tag = "kind", rename_all = "camelCase")]
277pub enum TransformNoChangeReasonV0 {
278    NoMutation,
279    EmissionBoundary,
280    ProfileNotApplicable {
281        profile: TransformEvaluationProfileV0,
282    },
283    NoMatchingSelectorRewrite,
284    DialectNotApplicable,
285}
286
287#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
288#[serde(
289    tag = "kind",
290    rename_all = "camelCase",
291    rename_all_fields = "camelCase"
292)]
293pub enum TransformBlockedReasonV0 {
294    MissingPrecondition {
295        precondition: TransformPreconditionV0,
296    },
297    PrecisionBelowFloor {
298        required: FactPrecision,
299        observed: FactPrecision,
300    },
301    DischargeMissing {
302        lookup_status: Option<DischargeLedgerLookupStatusV0>,
303        verdict: Option<DischargeLedgerVerdictV0>,
304    },
305    StrictVerification {
306        reasons: Vec<TransformStrictPolicyReasonV0>,
307    },
308    PassImplementation,
309    ClosedWorldAdmission {
310        reasons: Vec<TransformStrictPolicyReasonV0>,
311    },
312}
313
314#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
315#[serde(tag = "kind", rename_all = "camelCase")]
316pub enum TransformRejectionReasonV0 {
317    IrTransaction {
318        pass: TransformPassKind,
319    },
320    SemanticPreservation,
321    StrictVerification {
322        reasons: Vec<TransformStrictPolicyReasonV0>,
323    },
324    ModuleExportPreservation {
325        pass: TransformPassKind,
326        reason: String,
327    },
328}
329
330#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
331#[serde(
332    tag = "kind",
333    rename_all = "camelCase",
334    rename_all_fields = "camelCase"
335)]
336pub enum TransformStructuralDecisionClassV0 {
337    FactConsuming { required_precision: FactPrecision },
338    StaticExact,
339    ObligationDischarge,
340    NonRemovalRewrite,
341}
342
343#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
344#[serde(rename_all = "camelCase")]
345pub struct TransformStructuralDecisionPolicyV0 {
346    pub pass: TransformPassKind,
347    pub class: TransformStructuralDecisionClassV0,
348    pub reason: &'static str,
349}
350
351impl TransformStructuralDecisionPolicyV0 {
352    pub const fn new(
353        pass: TransformPassKind,
354        class: TransformStructuralDecisionClassV0,
355        reason: &'static str,
356    ) -> Self {
357        Self {
358            pass,
359            class,
360            reason,
361        }
362    }
363
364    pub const fn required_precision(self) -> Option<FactPrecision> {
365        match self.class {
366            TransformStructuralDecisionClassV0::FactConsuming { required_precision } => {
367                Some(required_precision)
368            }
369            TransformStructuralDecisionClassV0::StaticExact
370            | TransformStructuralDecisionClassV0::ObligationDischarge
371            | TransformStructuralDecisionClassV0::NonRemovalRewrite => None,
372        }
373    }
374}
375
376pub const TRANSFORM_STRUCTURAL_DECISION_POLICIES_V0: &[TransformStructuralDecisionPolicyV0] = &[
377    TransformStructuralDecisionPolicyV0::new(
378        TransformPassKind::ImportInline,
379        TransformStructuralDecisionClassV0::NonRemovalRewrite,
380        "materializes explicitly resolved imports without reachability pruning",
381    ),
382    TransformStructuralDecisionPolicyV0::new(
383        TransformPassKind::ResolveCssModulesComposes,
384        TransformStructuralDecisionClassV0::NonRemovalRewrite,
385        "materializes explicit CSS Modules composition resolution",
386    ),
387    TransformStructuralDecisionPolicyV0::new(
388        TransformPassKind::DesignTokenRouting,
389        TransformStructuralDecisionClassV0::NonRemovalRewrite,
390        "rewrites values through explicit design-token routes",
391    ),
392    TransformStructuralDecisionPolicyV0::new(
393        TransformPassKind::HashCssModuleClassNames,
394        TransformStructuralDecisionClassV0::NonRemovalRewrite,
395        "rewrites selectors through an explicit identity map",
396    ),
397    TransformStructuralDecisionPolicyV0::new(
398        TransformPassKind::RuleDeduplication,
399        TransformStructuralDecisionClassV0::StaticExact,
400        "removes only statically equivalent duplicate rules",
401    ),
402    TransformStructuralDecisionPolicyV0::new(
403        TransformPassKind::RuleMerging,
404        TransformStructuralDecisionClassV0::NonRemovalRewrite,
405        "combines adjacent declarations without reachability pruning",
406    ),
407    TransformStructuralDecisionPolicyV0::new(
408        TransformPassKind::SelectorMerging,
409        TransformStructuralDecisionClassV0::NonRemovalRewrite,
410        "combines equivalent selector blocks without reachability pruning",
411    ),
412    TransformStructuralDecisionPolicyV0::new(
413        TransformPassKind::NestingUnwrap,
414        TransformStructuralDecisionClassV0::NonRemovalRewrite,
415        "expands nested selectors without reachability pruning",
416    ),
417    TransformStructuralDecisionPolicyV0::new(
418        TransformPassKind::ScopeFlatten,
419        TransformStructuralDecisionClassV0::ObligationDischarge,
420        "requires accepted scope-flatten obligations",
421    ),
422    TransformStructuralDecisionPolicyV0::new(
423        TransformPassKind::LayerFlatten,
424        TransformStructuralDecisionClassV0::ObligationDischarge,
425        "requires accepted layer-flatten obligations",
426    ),
427    TransformStructuralDecisionPolicyV0::new(
428        TransformPassKind::SupportsStaticEval,
429        TransformStructuralDecisionClassV0::StaticExact,
430        "removes only statically decided supports branches",
431    ),
432    TransformStructuralDecisionPolicyV0::new(
433        TransformPassKind::MediaStaticEval,
434        TransformStructuralDecisionClassV0::StaticExact,
435        "removes only statically unsatisfiable media branches",
436    ),
437    TransformStructuralDecisionPolicyV0::new(
438        TransformPassKind::ContainerStaticEval,
439        TransformStructuralDecisionClassV0::StaticExact,
440        "removes only statically unsatisfiable container branches",
441    ),
442    TransformStructuralDecisionPolicyV0::new(
443        TransformPassKind::NativeCssStaticEval,
444        TransformStructuralDecisionClassV0::StaticExact,
445        "folds only statically evaluable native CSS expressions",
446    ),
447    TransformStructuralDecisionPolicyV0::new(
448        TransformPassKind::DeadMediaBranchRemoval,
449        TransformStructuralDecisionClassV0::StaticExact,
450        "removes only media branches selected by explicit static policy",
451    ),
452    TransformStructuralDecisionPolicyV0::new(
453        TransformPassKind::DeadSupportsBranchRemoval,
454        TransformStructuralDecisionClassV0::StaticExact,
455        "removes only statically decided supports branches",
456    ),
457    TransformStructuralDecisionPolicyV0::new(
458        TransformPassKind::TreeShakeClass,
459        TransformStructuralDecisionClassV0::FactConsuming {
460            required_precision: FactPrecision::Conservative,
461        },
462        "removes class rules only from a closed-world reachability over-approximation",
463    ),
464    TransformStructuralDecisionPolicyV0::new(
465        TransformPassKind::TreeShakeKeyframes,
466        TransformStructuralDecisionClassV0::FactConsuming {
467            required_precision: FactPrecision::Conservative,
468        },
469        "removes keyframes only from a closed-world reachability over-approximation",
470    ),
471    TransformStructuralDecisionPolicyV0::new(
472        TransformPassKind::TreeShakeValue,
473        TransformStructuralDecisionClassV0::FactConsuming {
474            required_precision: FactPrecision::Conservative,
475        },
476        "removes CSS Modules values only from a closed-world reachability over-approximation",
477    ),
478    TransformStructuralDecisionPolicyV0::new(
479        TransformPassKind::TreeShakeCustomProperty,
480        TransformStructuralDecisionClassV0::FactConsuming {
481            required_precision: FactPrecision::Conservative,
482        },
483        "removes custom properties only from a closed-world reachability over-approximation",
484    ),
485    TransformStructuralDecisionPolicyV0::new(
486        TransformPassKind::EmptyRuleRemoval,
487        TransformStructuralDecisionClassV0::StaticExact,
488        "removes only structurally empty rules",
489    ),
490];
491
492pub fn transform_structural_decision_policy(
493    pass: TransformPassKind,
494) -> Option<&'static TransformStructuralDecisionPolicyV0> {
495    TRANSFORM_STRUCTURAL_DECISION_POLICIES_V0
496        .iter()
497        .find(|policy| policy.pass == pass)
498}
499
500#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
501#[serde(tag = "kind", rename_all = "camelCase")]
502pub enum RollbackScopeV0 {
503    RejectPreservedInput,
504    InversePatch,
505    CommittedIrrecoverable,
506}
507
508#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
509#[serde(rename_all = "camelCase")]
510pub struct RollbackReceiptV0 {
511    pub pass_id: String,
512    pub attempted_mutation_count: Option<usize>,
513    pub input_content_signature: String,
514    #[serde(skip_serializing_if = "Option::is_none")]
515    pub output_preserved_content_signature: Option<String>,
516    pub restorable: RollbackScopeV0,
517}
518
519#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
520#[serde(rename_all = "camelCase")]
521pub struct TransformDischargeEvidenceV0 {
522    pub evidence_node_key: EvidenceNodeKeyV0,
523    pub guarantee_family: GuaranteeFamilyV0,
524    pub ledger_cell_key: String,
525    pub boundedness_kind: String,
526}
527
528/// Cascade dimensions covered by an observed winner-equality comparison.
529///
530/// Coverage is explicit so consumers do not mistake a partial observation for
531/// a guarantee over cascade dimensions that have no production driver yet.
532#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
533#[serde(rename_all = "camelCase")]
534pub enum TransformWinnerEqualityAxisV0 {
535    CascadeLevel,
536    LayerRank,
537    ScopeProximity,
538    Specificity,
539    SourceOrder,
540}
541
542/// Why a winner-equality observation could not cover one cascade dimension.
543#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
544#[serde(
545    tag = "kind",
546    rename_all = "camelCase",
547    rename_all_fields = "camelCase"
548)]
549pub enum TransformWinnerEqualityAbsenceReasonV0 {
550    DriverUnavailable {
551        level: Option<CascadeLevel>,
552    },
553    AffectedPairUnavailable,
554    SpecificityInexact,
555    WinnerNotDefinite,
556    WinnerChanged,
557    GuardedWinnerFunctionsDiffer {
558        input_root: GuardedCascadeWinnerRootV0,
559        output_root: GuardedCascadeWinnerRootV0,
560    },
561    GuardedWinnerPlaneDisagreement {
562        side: &'static str,
563        canonical_mtbdd: GuardedCascadeWinnerPlaneAnswerV0,
564        scenario_sweep: GuardedCascadeWinnerPlaneAnswerV0,
565    },
566}
567
568/// A typed precision boundary for a missing winner-equality observation.
569#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
570#[serde(rename_all = "camelCase")]
571pub struct TransformWinnerEqualityAbsenceV0 {
572    pub axis: TransformWinnerEqualityAxisV0,
573    pub reason: TransformWinnerEqualityAbsenceReasonV0,
574}
575
576/// The semantic location whose cascade winner is compared across a transform.
577#[derive(Debug, Clone, Serialize)]
578#[serde(rename_all = "camelCase")]
579pub struct TransformWinnerEqualityAffectedPairV0 {
580    pub element_signature: ElementSignature,
581    pub property: AuthoredPropertyTextV0,
582}
583
584impl PartialEq for TransformWinnerEqualityAffectedPairV0 {
585    fn eq(&self, other: &Self) -> bool {
586        self.element_signature == other.element_signature
587            && self
588                .property
589                .to_property_name()
590                .same_as(&other.property.to_property_name())
591    }
592}
593
594impl Eq for TransformWinnerEqualityAffectedPairV0 {}
595
596/// A definite winner and the proof emitted by the cascade authority.
597///
598/// Keeping the authority-owned types here prevents transform code from
599/// reconstructing winner order or proof fields independently.
600#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
601#[serde(rename_all = "camelCase")]
602pub struct TransformWinnerEqualityWitnessV0 {
603    pub winner: CascadeDeclaration,
604    pub proof: CascadeProof,
605}
606
607impl TransformWinnerEqualityWitnessV0 {
608    pub fn from_cascade_outcome(outcome: &CascadeOutcome) -> Option<Self> {
609        match outcome {
610            CascadeOutcome::Definite { winner, proof, .. } => Some(Self {
611                winner: winner.clone(),
612                proof: proof.as_ref().clone(),
613            }),
614            CascadeOutcome::RankedSet(_) | CascadeOutcome::Inherit | CascadeOutcome::Top => None,
615        }
616    }
617}
618
619/// Result of comparing authority-produced cascade witnesses for one affected pair.
620#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
621#[serde(
622    tag = "kind",
623    rename_all = "camelCase",
624    rename_all_fields = "camelCase"
625)]
626pub enum TransformWinnerEqualityObservationV0 {
627    ObservedEqual {
628        axes: Vec<TransformWinnerEqualityAxisV0>,
629        input: TransformWinnerEqualityWitnessV0,
630        output: TransformWinnerEqualityWitnessV0,
631    },
632    ObservedDifferent {
633        axes: Vec<TransformWinnerEqualityAxisV0>,
634        input: TransformWinnerEqualityWitnessV0,
635        output: TransformWinnerEqualityWitnessV0,
636    },
637    Absent {
638        reasons: Vec<TransformWinnerEqualityAbsenceV0>,
639    },
640    ObservedGuardedEqual {
641        axes: Vec<TransformWinnerEqualityAxisV0>,
642        input: TransformWinnerEqualityWitnessV0,
643        output: TransformWinnerEqualityWitnessV0,
644        authority: GuardedCascadeWinnerAuthorityV0,
645    },
646}
647
648/// A cascade-winner comparison requested for one admitted transform mutation.
649#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
650#[serde(rename_all = "camelCase")]
651pub struct TransformWinnerEqualityObligationV0 {
652    pub pass_id: &'static str,
653    pub affected_pair: TransformWinnerEqualityAffectedPairV0,
654    pub observation: TransformWinnerEqualityObservationV0,
655}
656
657#[derive(Debug, Clone, Default, PartialEq, Eq)]
658pub struct TransformExecutionPolicyV0 {
659    pub strict_policy: Option<TransformStrictPolicyDescriptorV0>,
660}
661
662impl TransformExecutionPolicyV0 {
663    pub fn for_profile(profile_id: &str) -> Option<Self> {
664        strict_policy_descriptor_for_profile(profile_id).map(|strict_policy| Self {
665            strict_policy: Some(strict_policy),
666        })
667    }
668}
669
670#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
671#[serde(
672    tag = "kind",
673    rename_all = "camelCase",
674    rename_all_fields = "camelCase"
675)]
676pub enum TransformStrictPolicyReasonV0 {
677    RequiredAxisUnavailable {
678        axis: TransformWinnerEqualityAxisV0,
679    },
680    CascadeEnvironmentUnavailable,
681    WinnerChanged {
682        axes: Vec<TransformWinnerEqualityAxisV0>,
683    },
684    ObservationUnavailable {
685        reasons: Vec<TransformWinnerEqualityAbsenceV0>,
686    },
687    UnknownPass,
688    ClosedWorldEvidenceUnavailable,
689    DecisionCoverageIncomplete,
690    ClosedWorldEvidenceIncomplete {
691        missing: Vec<String>,
692    },
693    LivenessNotClosed {
694        symbol: String,
695        from_module: ModuleInstanceKeyV0,
696        via_edge: &'static str,
697    },
698    EvidenceUnavailable,
699    OwnershipNotSeparable {
700        token: String,
701        module_paths: Vec<String>,
702    },
703}
704
705#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
706#[serde(rename_all = "camelCase")]
707#[non_exhaustive]
708pub enum CssModuleTokenCollisionPathScopeV0 {
709    BothPaths,
710    ImportInlineLegacyOnly,
711    LinkedOrderOnly,
712}
713
714impl CssModuleTokenCollisionPathScopeV0 {
715    pub const fn as_wire_label(self) -> &'static str {
716        match self {
717            Self::BothPaths => "bothPaths",
718            Self::ImportInlineLegacyOnly => "importInlineLegacyOnly",
719            Self::LinkedOrderOnly => "linkedOrderOnly",
720        }
721    }
722}
723
724#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
725#[serde(rename_all = "camelCase")]
726#[non_exhaustive]
727pub struct CssModuleTokenOwnershipV0 {
728    pub emitted_token: String,
729    pub module_instances: Vec<ModuleInstanceKeyV0>,
730    pub module_paths: Vec<String>,
731    pub original_names: Vec<String>,
732}
733
734impl CssModuleTokenOwnershipV0 {
735    pub fn new(
736        emitted_token: impl Into<String>,
737        module_instances: Vec<ModuleInstanceKeyV0>,
738        module_paths: Vec<String>,
739        original_names: Vec<String>,
740    ) -> Self {
741        Self {
742            emitted_token: emitted_token.into(),
743            module_instances,
744            module_paths,
745            original_names,
746        }
747    }
748}
749
750#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
751#[serde(rename_all = "camelCase")]
752#[non_exhaustive]
753pub struct CssModuleTokenCollisionV0 {
754    pub emitted_token: String,
755    pub module_instances: Vec<ModuleInstanceKeyV0>,
756    pub module_paths: Vec<String>,
757    pub original_names: Vec<String>,
758    pub observed_emission_paths: Vec<&'static str>,
759    pub path_scope: CssModuleTokenCollisionPathScopeV0,
760}
761
762impl CssModuleTokenCollisionV0 {
763    pub fn new(
764        ownership: CssModuleTokenOwnershipV0,
765        observed_emission_paths: Vec<&'static str>,
766        path_scope: CssModuleTokenCollisionPathScopeV0,
767    ) -> Self {
768        Self {
769            emitted_token: ownership.emitted_token,
770            module_instances: ownership.module_instances,
771            module_paths: ownership.module_paths,
772            original_names: ownership.original_names,
773            observed_emission_paths,
774            path_scope,
775        }
776    }
777}
778
779#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
780#[serde(rename_all = "camelCase")]
781#[non_exhaustive]
782pub struct CssModuleTokenInterfaceMismatchV0 {
783    pub module_instance: ModuleInstanceKeyV0,
784    pub module_path: String,
785    pub original_name: String,
786    pub promised_token: String,
787    pub emitted_token: String,
788}
789
790impl CssModuleTokenInterfaceMismatchV0 {
791    pub fn new(
792        module_instance: ModuleInstanceKeyV0,
793        module_path: impl Into<String>,
794        original_name: impl Into<String>,
795        promised_token: impl Into<String>,
796        emitted_token: impl Into<String>,
797    ) -> Self {
798        Self {
799            module_instance,
800            module_path: module_path.into(),
801            original_name: original_name.into(),
802            promised_token: promised_token.into(),
803            emitted_token: emitted_token.into(),
804        }
805    }
806}
807
808#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
809#[serde(rename_all = "camelCase")]
810#[non_exhaustive]
811pub struct CssModuleTokenOwnershipCensusV0 {
812    pub schema_version: &'static str,
813    pub product: &'static str,
814    pub scope: &'static str,
815    pub emission_path: &'static str,
816    pub complete: bool,
817    pub modeled_preimage_count: usize,
818    pub emitted_token_count: usize,
819    pub token_ownerships: Vec<CssModuleTokenOwnershipV0>,
820    pub module_token_collision_count: usize,
821    pub module_token_collisions: Vec<CssModuleTokenCollisionV0>,
822    pub unattributed_emitted_tokens: Vec<String>,
823    pub interface_mismatches: Vec<CssModuleTokenInterfaceMismatchV0>,
824    pub unavailable_reasons: Vec<String>,
825}
826
827impl CssModuleTokenOwnershipCensusV0 {
828    pub fn new(
829        emission_path: &'static str,
830        modeled_preimage_count: usize,
831        token_ownerships: Vec<CssModuleTokenOwnershipV0>,
832        module_token_collisions: Vec<CssModuleTokenCollisionV0>,
833        unattributed_emitted_tokens: Vec<String>,
834        interface_mismatches: Vec<CssModuleTokenInterfaceMismatchV0>,
835    ) -> Self {
836        let module_token_collision_count = module_token_collisions.len();
837        let emitted_token_count = token_ownerships.len() + unattributed_emitted_tokens.len();
838        Self {
839            schema_version: "0",
840            product: "omena-query.css-module-token-ownership-census",
841            scope: "bundleEmission",
842            emission_path,
843            complete: unattributed_emitted_tokens.is_empty(),
844            modeled_preimage_count,
845            emitted_token_count,
846            token_ownerships,
847            module_token_collision_count,
848            module_token_collisions,
849            unattributed_emitted_tokens,
850            interface_mismatches,
851            unavailable_reasons: Vec::new(),
852        }
853    }
854
855    pub fn unavailable(emission_path: &'static str, reason: impl Into<String>) -> Self {
856        Self {
857            schema_version: "0",
858            product: "omena-query.css-module-token-ownership-census",
859            scope: "bundleEmission",
860            emission_path,
861            complete: false,
862            modeled_preimage_count: 0,
863            emitted_token_count: 0,
864            token_ownerships: Vec::new(),
865            module_token_collision_count: 0,
866            module_token_collisions: Vec::new(),
867            unattributed_emitted_tokens: Vec::new(),
868            interface_mismatches: Vec::new(),
869            unavailable_reasons: vec![reason.into()],
870        }
871    }
872}
873
874#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
875#[serde(rename_all = "camelCase")]
876pub struct TransformStrictPolicyEventV0 {
877    pub pass_id: String,
878    pub reasons: Vec<TransformStrictPolicyReasonV0>,
879}
880
881#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
882#[serde(rename_all = "camelCase")]
883pub struct TransformStrictPolicySummaryV0 {
884    pub profile_id: Option<String>,
885    pub refused_count: usize,
886    pub rolled_back_count: usize,
887    pub refusal_reasons: Vec<TransformStrictPolicyEventV0>,
888    pub rollback_reasons: Vec<TransformStrictPolicyEventV0>,
889}
890
891impl TransformStrictPolicySummaryV0 {
892    pub fn for_profile(profile_id: &str) -> Self {
893        Self {
894            profile_id: Some(profile_id.to_string()),
895            ..Self::default()
896        }
897    }
898
899    pub fn record_refusal(
900        &mut self,
901        pass_id: impl Into<String>,
902        reasons: Vec<TransformStrictPolicyReasonV0>,
903    ) {
904        self.refusal_reasons.push(TransformStrictPolicyEventV0 {
905            pass_id: pass_id.into(),
906            reasons,
907        });
908        self.refused_count = self.refusal_reasons.len();
909    }
910
911    pub fn record_rollback(
912        &mut self,
913        pass_id: impl Into<String>,
914        reasons: Vec<TransformStrictPolicyReasonV0>,
915    ) {
916        self.rollback_reasons.push(TransformStrictPolicyEventV0 {
917            pass_id: pass_id.into(),
918            reasons,
919        });
920        self.rolled_back_count = self.rollback_reasons.len();
921    }
922}
923
924#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
925pub struct ClosedWorldAdmissionTierV0;
926
927#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
928#[serde(rename_all = "camelCase")]
929pub struct TransformClosedWorldAdmissionEventV0 {
930    pub pass_id: String,
931    pub module_instance: Option<ModuleInstanceKeyV0>,
932    pub reasons: Vec<TransformStrictPolicyReasonV0>,
933}
934
935#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
936#[serde(rename_all = "camelCase")]
937pub struct TransformClosedWorldAdmissionSummaryV0 {
938    pub refused_count: usize,
939    #[serde(skip_serializing_if = "Option::is_none")]
940    pub evidence_scope: Option<&'static str>,
941    pub refusal_reasons: Vec<TransformClosedWorldAdmissionEventV0>,
942}
943
944impl TransformClosedWorldAdmissionSummaryV0 {
945    pub fn record_refusal(
946        &mut self,
947        pass_id: impl Into<String>,
948        module_instance: Option<ModuleInstanceKeyV0>,
949        reasons: Vec<TransformStrictPolicyReasonV0>,
950    ) {
951        self.refusal_reasons
952            .push(TransformClosedWorldAdmissionEventV0 {
953                pass_id: pass_id.into(),
954                module_instance,
955                reasons,
956            });
957        self.refused_count = self.refusal_reasons.len();
958    }
959}
960
961/// Trust carried by an admitted transform decision.
962///
963/// This is descriptive evidence for default and other non-strict profiles. The
964/// enum itself never participates in admission. An opt-in strict profile may
965/// separately enforce the underlying typed obligations while leaving the base
966/// admission predicate unchanged.
967#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
968#[serde(
969    tag = "kind",
970    rename_all = "camelCase",
971    rename_all_fields = "camelCase"
972)]
973pub enum TransformSemanticGuaranteeTierV0 {
974    L0Observed,
975    WinnerEqualityObserved {
976        axes: Vec<TransformWinnerEqualityAxisV0>,
977    },
978    Absent {
979        reasons: Vec<TransformWinnerEqualityAbsenceV0>,
980    },
981}
982
983impl RollbackReceiptV0 {
984    pub fn preserves_rejected_input(&self) -> bool {
985        self.restorable == RollbackScopeV0::RejectPreservedInput
986            && self.output_preserved_content_signature.as_deref()
987                == Some(self.input_content_signature.as_str())
988    }
989
990    pub fn covers_inverse_patch(
991        &self,
992        inverse_patch_count: usize,
993        input_content_signature: &str,
994    ) -> bool {
995        self.restorable == RollbackScopeV0::InversePatch
996            && self.attempted_mutation_count == Some(inverse_patch_count)
997            && self.input_content_signature == input_content_signature
998            && self.output_preserved_content_signature.is_none()
999    }
1000}
1001
1002#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1003#[serde(
1004    tag = "kind",
1005    rename_all = "camelCase",
1006    rename_all_fields = "camelCase"
1007)]
1008pub enum TransformDecision {
1009    Applied {
1010        outcome: TransformPassExecutionOutcomeV0,
1011        rollback_receipt: RollbackReceiptV0,
1012        #[serde(skip_serializing_if = "Option::is_none")]
1013        semantic_guarantee_tier: Option<TransformSemanticGuaranteeTierV0>,
1014        #[serde(skip_serializing_if = "Vec::is_empty")]
1015        discharge_evidence: Vec<TransformDischargeEvidenceV0>,
1016    },
1017    NoChange {
1018        reason: TransformNoChangeReasonV0,
1019        outcome: TransformPassExecutionOutcomeV0,
1020    },
1021    Blocked {
1022        reason: TransformBlockedReasonV0,
1023        outcome: TransformPassExecutionOutcomeV0,
1024    },
1025    Rejected {
1026        reason: TransformRejectionReasonV0,
1027        outcome: TransformPassExecutionOutcomeV0,
1028        rollback_receipt: RollbackReceiptV0,
1029    },
1030}
1031
1032impl TransformDecision {
1033    pub fn compatibility_outcome(&self) -> &TransformPassExecutionOutcomeV0 {
1034        match self {
1035            Self::Applied { outcome, .. }
1036            | Self::NoChange { outcome, .. }
1037            | Self::Blocked { outcome, .. }
1038            | Self::Rejected { outcome, .. } => outcome,
1039        }
1040    }
1041
1042    pub fn into_compatibility_outcome(self) -> TransformPassExecutionOutcomeV0 {
1043        match self {
1044            Self::Applied { outcome, .. }
1045            | Self::NoChange { outcome, .. }
1046            | Self::Blocked { outcome, .. }
1047            | Self::Rejected { outcome, .. } => outcome,
1048        }
1049    }
1050
1051    pub fn rollback_receipt(&self) -> Option<&RollbackReceiptV0> {
1052        match self {
1053            Self::Applied {
1054                rollback_receipt, ..
1055            }
1056            | Self::Rejected {
1057                rollback_receipt, ..
1058            } => Some(rollback_receipt),
1059            Self::NoChange { .. } | Self::Blocked { .. } => None,
1060        }
1061    }
1062
1063    pub fn semantic_guarantee_tier(&self) -> Option<&TransformSemanticGuaranteeTierV0> {
1064        match self {
1065            Self::Applied {
1066                semantic_guarantee_tier,
1067                ..
1068            } => semantic_guarantee_tier.as_ref(),
1069            Self::NoChange { .. } | Self::Blocked { .. } | Self::Rejected { .. } => None,
1070        }
1071    }
1072}
1073
1074#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1075#[serde(rename_all = "camelCase")]
1076pub struct TransformProvenanceDerivationForestV0 {
1077    pub schema_version: &'static str,
1078    pub product: &'static str,
1079    pub root_count: usize,
1080    pub node_count: usize,
1081    pub nodes: Vec<TransformProvenanceDerivationNodeV0>,
1082}
1083
1084impl TransformProvenanceDerivationForestV0 {
1085    pub fn evidence_graph(&self) -> Result<EvidenceGraphV0, EvidenceGraphBuildErrorV0> {
1086        build_evidence_graph_from_edges_v0(
1087            self.nodes
1088                .iter()
1089                .map(TransformProvenanceDerivationNodeV0::evidence_node_seed),
1090            self.nodes
1091                .iter()
1092                .map(TransformProvenanceDerivationNodeV0::evidence_demand_edge),
1093        )
1094    }
1095}
1096
1097#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1098#[serde(rename_all = "camelCase")]
1099pub struct TransformProvenanceDerivationNodeV0 {
1100    pub node_index: usize,
1101    pub parent_index: Option<usize>,
1102    pub pass_id: &'static str,
1103    pub status: TransformPassRuntimeStatus,
1104    pub input_byte_len: usize,
1105    pub output_byte_len: usize,
1106    pub source_span_start: usize,
1107    pub source_span_end: usize,
1108    pub generated_span_start: usize,
1109    pub generated_span_end: usize,
1110    pub mutation_spans: Vec<TransformProvenanceMutationSpanV0>,
1111    pub mutation_count: usize,
1112    pub provenance_preserved: bool,
1113    pub detail: &'static str,
1114}
1115
1116impl TransformProvenanceDerivationNodeV0 {
1117    pub fn evidence_node_key(&self) -> EvidenceNodeKeyV0 {
1118        EvidenceNodeKeyV0::new(
1119            TRANSFORM_PROVENANCE_NODE_EVIDENCE_QUERY_V0,
1120            format!("{}#{}", self.pass_id, self.node_index),
1121        )
1122    }
1123
1124    pub fn evidence_node_seed(&self) -> EvidenceNodeSeedV0 {
1125        EvidenceNodeSeedV0::new(
1126            self.evidence_node_key(),
1127            vec![
1128                ["pass:", self.pass_id].concat(),
1129                ["detail:", self.detail].concat(),
1130                ["mutationCount:", self.mutation_count.to_string().as_str()].concat(),
1131                [
1132                    "provenancePreserved:",
1133                    self.provenance_preserved.to_string().as_str(),
1134                ]
1135                .concat(),
1136            ],
1137            GuaranteeKindV0::for_label_less_family(),
1138        )
1139    }
1140
1141    pub fn evidence_demand_edge(&self) -> EvidenceDemandEdgeV0 {
1142        EvidenceDemandEdgeV0::new(
1143            TRANSFORM_PROVENANCE_NODE_EVIDENCE_QUERY_V0,
1144            self.evidence_node_key(),
1145            TRANSFORM_EVIDENCE_EDGE_KIND_V0,
1146        )
1147    }
1148}
1149
1150#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1151#[serde(rename_all = "camelCase")]
1152pub struct TransformProvenanceMutationSpanV0 {
1153    pub source_span_start: usize,
1154    pub source_span_end: usize,
1155    pub generated_span_start: usize,
1156    pub generated_span_end: usize,
1157    #[serde(skip_serializing_if = "Option::is_none")]
1158    pub node_key: Option<StableNodeKeyV0>,
1159}
1160
1161/// Counts incremental lex-splice outcomes inside a transform execution.
1162///
1163/// A fallback is conservative: the cache declines to reuse token ranges and the
1164/// next consumer re-lexes the generated source normally.
1165#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
1166#[serde(rename_all = "camelCase")]
1167pub struct TransformLexCacheSpliceTelemetryV0 {
1168    /// Number of generated token streams inserted through bounded splicing.
1169    pub splice_hit_count: u64,
1170    /// Number of active-cache attempts that intentionally fell back to full re-lex.
1171    pub full_relex_fallback_count: u64,
1172    /// Fallbacks caused by invalid or non-projectable mutation windows.
1173    pub window_derivation_fallback_count: u64,
1174    /// Fallbacks where the safe restart window covers the full generated output.
1175    pub full_output_window_fallback_count: u64,
1176    /// Fallbacks caused by token offset arithmetic or projection failure.
1177    pub token_offset_fallback_count: u64,
1178}
1179
1180/// Counts structural IR transaction outcomes that matter for String-currency
1181/// retirement.
1182#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
1183#[serde(rename_all = "camelCase")]
1184#[non_exhaustive]
1185pub struct TransformStructuralIrTransactionTelemetryV0 {
1186    pub transaction_commit_count: u64,
1187    pub ir_metadata_refresh_count: u64,
1188    pub ir_transaction_commit_count: u64,
1189    pub ir_materialization_count: u64,
1190    pub ir_mutation_count: u64,
1191}
1192
1193#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1194#[serde(rename_all = "camelCase")]
1195pub enum TransformSemanticObservationKeyAxisV0 {
1196    Selector,
1197    Property,
1198    Context,
1199}
1200
1201#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1202#[serde(rename_all = "camelCase")]
1203pub enum TransformSemanticObservationValueAxisV0 {
1204    Value,
1205    Important,
1206}
1207
1208#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1209#[serde(rename_all = "camelCase")]
1210pub enum TransformSemanticObservationOrderingRuleV0 {
1211    SourceOrder,
1212    ImportantPrecedence,
1213}
1214
1215#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1216#[serde(rename_all = "camelCase")]
1217pub enum TransformSemanticUnobservedAxisV0 {
1218    InterSelectorSpecificityCompetition,
1219    CascadeLayerOrder,
1220    Origin,
1221    ScopeProximity,
1222    DomDependentMatching,
1223    Inheritance,
1224    CustomPropertyEnvironment,
1225    AnimationAndTransition,
1226    NonStyleRuleDeclarationCarriers,
1227    IntraRuleDeclarationOrdering,
1228}
1229
1230#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1231#[serde(rename_all = "camelCase")]
1232pub enum TransformSemanticPreservationClaimScopeV0 {
1233    ObservedSurfaceOnly,
1234}
1235
1236#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
1237#[serde(rename_all = "camelCase")]
1238pub enum TransformSemanticPreservationVocabularyReviewV0 {
1239    DeferredUntilFullCascadeObservation,
1240}
1241
1242/// Declares exactly which semantic projection the transform guard compares.
1243#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1244#[serde(rename_all = "camelCase")]
1245pub struct TransformSemanticObservationSurfaceV0 {
1246    pub key_axes: Vec<TransformSemanticObservationKeyAxisV0>,
1247    pub value_axes: Vec<TransformSemanticObservationValueAxisV0>,
1248    pub ordering_rules: Vec<TransformSemanticObservationOrderingRuleV0>,
1249    pub unobserved_axes: Vec<TransformSemanticUnobservedAxisV0>,
1250    pub claim_scope: TransformSemanticPreservationClaimScopeV0,
1251    pub vocabulary_review: TransformSemanticPreservationVocabularyReviewV0,
1252}
1253
1254#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1255#[serde(rename_all = "camelCase")]
1256pub struct TransformSemanticPreservationTelemetryV0 {
1257    pub observed_pass_count: u64,
1258    pub preserved_pass_count: u64,
1259    pub blocked_pass_count: u64,
1260    pub observed_surface: TransformSemanticObservationSurfaceV0,
1261}
1262
1263#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
1264#[serde(rename_all = "camelCase")]
1265pub struct TransformDischargeLedgerTelemetryV0 {
1266    pub lookup_count: u64,
1267    pub matched_lookup_count: u64,
1268    pub accepted_stamp_count: u64,
1269    pub blocked_lookup_count: u64,
1270}
1271
1272#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1273#[serde(rename_all = "camelCase")]
1274pub struct TransformModuleExportPreservationEvidenceV0 {
1275    pub pass_id: &'static str,
1276    pub module_instance: ModuleInstanceKeyV0,
1277    pub before_premise_digest: String,
1278    pub after_premise_digest: String,
1279    pub catalog_schema_id: &'static str,
1280    pub catalog_content_digest: String,
1281    pub checked_rule_ids: Vec<String>,
1282}
1283
1284#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
1285#[serde(rename_all = "camelCase")]
1286pub struct TransformModuleExportPreservationTelemetryV0 {
1287    pub certificate_construction_count: u64,
1288    pub checked_token_count: u64,
1289    pub rejected_certificate_count: u64,
1290    pub evidence: Vec<TransformModuleExportPreservationEvidenceV0>,
1291}
1292
1293impl TransformModuleExportPreservationTelemetryV0 {
1294    pub fn is_empty(&self) -> bool {
1295        self.certificate_construction_count == 0
1296            && self.checked_token_count == 0
1297            && self.rejected_certificate_count == 0
1298            && self.evidence.is_empty()
1299    }
1300}
1301
1302#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1303#[serde(rename_all = "camelCase")]
1304pub struct TransformExecutionSummaryV0 {
1305    pub schema_version: &'static str,
1306    pub product: &'static str,
1307    pub input_byte_len: usize,
1308    pub output_byte_len: usize,
1309    pub requested_pass_ids: Vec<&'static str>,
1310    pub ordered_pass_ids: Vec<&'static str>,
1311    pub executed_pass_ids: Vec<&'static str>,
1312    pub planned_only_pass_ids: Vec<&'static str>,
1313    pub mutation_count: usize,
1314    pub provenance_preserved: bool,
1315    pub output_css: String,
1316    pub css_module_evaluation: Option<TransformModuleEvaluationV0>,
1317    pub css_import_inlines: Vec<TransformImportInlineV0>,
1318    pub css_module_composes_exports: Vec<TransformCssModuleComposesResolutionV0>,
1319    pub design_token_routes: Vec<TransformDesignTokenRouteV0>,
1320    pub semantic_removals: Vec<TransformSemanticRemovalV0>,
1321    #[serde(skip_serializing_if = "Option::is_none")]
1322    pub module_qualified_shake: Option<TransformModuleQualifiedShakeSummaryV0>,
1323    pub cascade_proof_obligations: TransformCascadeProofObligationReportV0,
1324    #[serde(skip_serializing_if = "Vec::is_empty")]
1325    pub winner_equality_obligations: Vec<TransformWinnerEqualityObligationV0>,
1326    pub provenance_derivation_forest: TransformProvenanceDerivationForestV0,
1327    pub structural_ir_transaction_telemetry: TransformStructuralIrTransactionTelemetryV0,
1328    pub semantic_preservation_telemetry: TransformSemanticPreservationTelemetryV0,
1329    pub discharge_ledger_telemetry: TransformDischargeLedgerTelemetryV0,
1330    #[serde(skip_serializing_if = "TransformModuleExportPreservationTelemetryV0::is_empty")]
1331    pub module_export_preservation_telemetry: TransformModuleExportPreservationTelemetryV0,
1332    pub strict_policy: TransformStrictPolicySummaryV0,
1333    pub closed_world_admission: TransformClosedWorldAdmissionSummaryV0,
1334    pub decisions: Vec<TransformDecision>,
1335    pub outcomes: Vec<TransformPassExecutionOutcomeV0>,
1336    pub pass_plan: TransformPassPlanV0,
1337}
1338
1339#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1340#[serde(rename_all = "camelCase")]
1341pub struct TransformModuleQualifiedShakeSummaryV0 {
1342    pub module_instance: ModuleInstanceKeyV0,
1343    pub removed_count: usize,
1344}
1345
1346#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1347#[serde(
1348    tag = "kind",
1349    rename_all = "camelCase",
1350    rename_all_fields = "camelCase"
1351)]
1352pub enum TransformModuleQualifiedExecutionErrorV0 {
1353    UnknownModuleInstance {
1354        module_instance: ModuleInstanceKeyV0,
1355    },
1356}
1357
1358#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1359#[serde(rename_all = "camelCase")]
1360pub struct TransformCascadeProofObligationReportV0 {
1361    pub schema_version: &'static str,
1362    pub product: &'static str,
1363    pub obligation_count: usize,
1364    pub accepted_count: usize,
1365    pub blocked_count: usize,
1366    pub checked_pass_ids: Vec<&'static str>,
1367    pub obligations: Vec<TransformCascadeProofObligationV0>,
1368}
1369
1370#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1371#[serde(rename_all = "camelCase")]
1372pub struct TransformCascadeProofObligationV0 {
1373    pub pass_id: &'static str,
1374    pub proof_product: &'static str,
1375    pub accepted: bool,
1376    pub blocked_reason: Option<String>,
1377    pub provenance_preserved: bool,
1378    pub cascade_safe_witness: String,
1379    pub source_span_start: Option<usize>,
1380    pub source_span_end: Option<usize>,
1381    pub checked_obligations: Vec<&'static str>,
1382    #[serde(skip_serializing_if = "Option::is_none")]
1383    pub canonical_smt_input: Option<CanonicalSmtInputV0>,
1384    #[serde(skip_serializing_if = "Option::is_none")]
1385    pub discharge_ledger_lookup: Option<DischargeLedgerLookupV0>,
1386    #[serde(skip_serializing_if = "Option::is_none")]
1387    pub discharge_evidence: Option<TransformDischargeEvidenceV0>,
1388    pub proof_payload: Value,
1389}
1390
1391#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1392#[serde(rename_all = "camelCase")]
1393pub struct TransformSemanticRemovalV0 {
1394    pub pass_id: &'static str,
1395    pub symbol_kind: &'static str,
1396    pub name: String,
1397    pub source_span_start: usize,
1398    pub source_span_end: usize,
1399    pub reason: &'static str,
1400    pub certainty: &'static str,
1401    pub derivation_steps: Vec<&'static str>,
1402}
1403
1404#[derive(Debug, Clone, PartialEq, Eq)]
1405pub(crate) struct TransformSemanticRemovalCandidate {
1406    pub(crate) symbol_kind: &'static str,
1407    pub(crate) name: String,
1408    pub(crate) source_span_start: usize,
1409    pub(crate) source_span_end: usize,
1410    pub(crate) reason: &'static str,
1411}
1412
1413impl TransformSemanticRemovalCandidate {
1414    pub(crate) fn into_public(self, pass_id: &'static str) -> TransformSemanticRemovalV0 {
1415        TransformSemanticRemovalV0 {
1416            pass_id,
1417            symbol_kind: self.symbol_kind,
1418            name: self.name,
1419            source_span_start: self.source_span_start,
1420            source_span_end: self.source_span_end,
1421            reason: self.reason,
1422            certainty: "high",
1423            derivation_steps: vec![
1424                "closedStyleWorld",
1425                "reachableRootSetComputed",
1426                "symbolNotMarkedReachable",
1427                "sourceRangeRemoved",
1428            ],
1429        }
1430    }
1431}
1432
1433#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1434#[serde(rename_all = "camelCase")]
1435pub struct TransformIncrementalExecutionSummaryV0 {
1436    pub schema_version: &'static str,
1437    pub product: &'static str,
1438    pub incremental_engine: &'static str,
1439    pub query_model: &'static str,
1440    pub reuse_policy: &'static str,
1441    pub reused_previous_execution: bool,
1442    pub incremental_plan: IncrementalComputationPlanV0,
1443    pub next_snapshot: IncrementalSnapshotV0,
1444    pub execution: TransformExecutionSummaryV0,
1445    pub ready_surfaces: Vec<&'static str>,
1446}
1447
1448#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1449#[serde(rename_all = "camelCase")]
1450pub struct TransformCascadeSafetyFuzzCaseV0 {
1451    pub seed: u64,
1452    pub pass_count: usize,
1453}
1454
1455#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1456#[serde(rename_all = "camelCase")]
1457pub struct TransformCascadeSafetyFuzzResultV0 {
1458    pub seed: u64,
1459    pub pass_count: usize,
1460    pub requested_pass_ids: Vec<&'static str>,
1461    pub executed_pass_ids: Vec<&'static str>,
1462    pub output_byte_len: usize,
1463    pub output_token_count: usize,
1464    pub output_error_count: usize,
1465    pub provenance_node_count: usize,
1466    pub passed: bool,
1467}
1468
1469#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1470#[serde(rename_all = "camelCase")]
1471pub struct TransformFuzzSeedReportV0 {
1472    pub schema_version: &'static str,
1473    pub product: &'static str,
1474    pub case_count: usize,
1475    pub passed_count: usize,
1476    pub failed_count: usize,
1477    pub results: Vec<TransformCascadeSafetyFuzzResultV0>,
1478}
1479
1480#[derive(Debug, Clone, Default, Deserialize, Serialize)]
1481#[serde(default, rename_all = "camelCase")]
1482pub struct TransformExecutionContextV0 {
1483    pub drop_dark_mode_media_queries: bool,
1484    pub supports_target_capability: Option<SupportsTargetCapabilityV0>,
1485    pub vendor_prefix_policy: Option<TransformVendorPrefixPolicyV0>,
1486    pub reachable_class_names: Vec<String>,
1487    pub reachable_keyframe_names: Vec<String>,
1488    pub reachable_value_names: Vec<String>,
1489    pub reachable_custom_property_names: Vec<AuthoredPropertyTextV0>,
1490    pub scss_module_evaluation: Option<TransformModuleEvaluationV0>,
1491    pub less_module_evaluation: Option<TransformModuleEvaluationV0>,
1492    pub import_inlines: Vec<TransformImportInlineV0>,
1493    pub class_name_rewrites: Vec<TransformClassNameRewriteV0>,
1494    pub css_module_composes_resolutions: Vec<TransformCssModuleComposesResolutionV0>,
1495    pub css_module_value_resolutions: Vec<TransformCssModuleValueResolutionV0>,
1496    pub design_token_routes: Vec<TransformDesignTokenRouteV0>,
1497    /// Complete declarations outside the transformed stylesheet that may
1498    /// participate in the cascade. Absence keeps winner trust fail-closed.
1499    #[serde(default, skip_serializing_if = "Option::is_none")]
1500    pub cascade_environment: Option<TransformCascadeEnvironmentV0>,
1501}
1502
1503impl PartialEq for TransformExecutionContextV0 {
1504    fn eq(&self, other: &Self) -> bool {
1505        self.drop_dark_mode_media_queries == other.drop_dark_mode_media_queries
1506            && self.supports_target_capability == other.supports_target_capability
1507            && self.vendor_prefix_policy == other.vendor_prefix_policy
1508            && self.reachable_class_names == other.reachable_class_names
1509            && self.reachable_keyframe_names == other.reachable_keyframe_names
1510            && self.reachable_value_names == other.reachable_value_names
1511            && authored_custom_property_sequences_same(
1512                &self.reachable_custom_property_names,
1513                &other.reachable_custom_property_names,
1514            )
1515            && self.scss_module_evaluation == other.scss_module_evaluation
1516            && self.less_module_evaluation == other.less_module_evaluation
1517            && self.import_inlines == other.import_inlines
1518            && self.class_name_rewrites == other.class_name_rewrites
1519            && self.css_module_composes_resolutions == other.css_module_composes_resolutions
1520            && self.css_module_value_resolutions == other.css_module_value_resolutions
1521            && self.design_token_routes == other.design_token_routes
1522            && self.cascade_environment == other.cascade_environment
1523    }
1524}
1525
1526impl Eq for TransformExecutionContextV0 {}
1527
1528fn authored_custom_property_sequences_same(
1529    left: &[AuthoredPropertyTextV0],
1530    right: &[AuthoredPropertyTextV0],
1531) -> bool {
1532    left.len() == right.len()
1533        && left
1534            .iter()
1535            .zip(right)
1536            .all(|(left, right)| left.to_custom_key() == right.to_custom_key())
1537}
1538
1539#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
1540#[serde(default, rename_all = "camelCase")]
1541pub struct TransformCascadeEnvironmentV0 {
1542    /// Global source-order coordinate assigned to the first declaration in
1543    /// the transformed stylesheet.
1544    pub stylesheet_source_order_base: u32,
1545    pub declarations: Vec<TransformCascadeEnvironmentDeclarationV0>,
1546}
1547
1548#[derive(Debug, Clone, Deserialize, Serialize)]
1549#[serde(rename_all = "camelCase")]
1550pub struct TransformCascadeEnvironmentDeclarationV0 {
1551    pub declaration_id: String,
1552    pub selector: String,
1553    pub property: AuthoredPropertyTextV0,
1554    pub value: String,
1555    pub origin: CascadeOriginV0,
1556    pub important: bool,
1557    #[serde(default, skip_serializing_if = "Option::is_none")]
1558    pub layer_rank: Option<i32>,
1559    #[serde(default, skip_serializing_if = "Option::is_none")]
1560    pub scope_proximity: Option<u32>,
1561    pub source_order: u32,
1562}
1563
1564impl PartialEq for TransformCascadeEnvironmentDeclarationV0 {
1565    fn eq(&self, other: &Self) -> bool {
1566        self.declaration_id == other.declaration_id
1567            && self.selector == other.selector
1568            && self
1569                .property
1570                .to_property_name()
1571                .same_as(&other.property.to_property_name())
1572            && self.value == other.value
1573            && self.origin == other.origin
1574            && self.important == other.important
1575            && self.layer_rank == other.layer_rank
1576            && self.scope_proximity == other.scope_proximity
1577            && self.source_order == other.source_order
1578    }
1579}
1580
1581impl Eq for TransformCascadeEnvironmentDeclarationV0 {}
1582
1583#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
1584#[serde(rename_all = "camelCase")]
1585pub struct TransformVendorPrefixPolicyV0 {
1586    pub webkit: bool,
1587    pub moz: bool,
1588    pub ms: bool,
1589}
1590
1591impl TransformVendorPrefixPolicyV0 {
1592    pub const fn none() -> Self {
1593        Self {
1594            webkit: false,
1595            moz: false,
1596            ms: false,
1597        }
1598    }
1599
1600    pub const fn conservative() -> Self {
1601        Self {
1602            webkit: true,
1603            moz: true,
1604            ms: true,
1605        }
1606    }
1607
1608    pub const fn is_empty(self) -> bool {
1609        !(self.webkit || self.moz || self.ms)
1610    }
1611
1612    pub fn allows_prefix(self, prefixed_name: &str) -> bool {
1613        if prefixed_name.starts_with("-webkit-") {
1614            return self.webkit;
1615        }
1616        if prefixed_name.starts_with("-moz-") {
1617            return self.moz;
1618        }
1619        if prefixed_name.starts_with("-ms-") {
1620            return self.ms;
1621        }
1622        true
1623    }
1624}
1625
1626#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1627#[serde(rename_all = "camelCase")]
1628pub struct TransformModuleEvaluationV0 {
1629    pub evaluator: String,
1630    #[serde(default, skip_serializing_if = "Option::is_none")]
1631    pub product_output_source: Option<String>,
1632    pub evaluated_css: String,
1633    #[serde(default, skip_serializing_if = "Option::is_none")]
1634    pub native_edit_output: Option<String>,
1635    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1636    pub native_replacements: Vec<TransformModuleEvaluationNativeReplacementV0>,
1637    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1638    pub native_edits: Vec<TransformModuleEvaluationNativeEditV0>,
1639    #[serde(default, skip_serializing_if = "Option::is_none")]
1640    pub oracle: Option<TransformModuleEvaluationOracleV0>,
1641}
1642
1643impl TransformModuleEvaluationV0 {
1644    pub fn declares_native_product_output(&self) -> bool {
1645        self.product_output_source
1646            .as_deref()
1647            .is_some_and(|source| source == "nativeEditOutput")
1648    }
1649
1650    // HONESTY NOTE: `divergence_count == 0` is a value-WELL-FORMEDNESS self-check on the
1651    // native-edit output (every native-emitted declaration value canonically round-trips), NOT a
1652    // differential against an external SCSS/Less compiler. So this gate means "native output is
1653    // self-consistent and value-preserving", NOT "native agrees with dart-sass/lessc". External
1654    // agreement is witnessed separately by the `externalDifferential` gate
1655    // (`scripts/check-rust-omena-diff-test-external-corpus-differential.ts`, pinned dart-sass/lessc) over
1656    // its covered fixture slices only; this self-check stays the cheap inner oracle for every
1657    // evaluated candidate, and the production rail remains a self-comparison.
1658    pub fn oracle_allows_native_product_output(&self) -> bool {
1659        self.oracle.as_ref().is_some_and(|oracle| {
1660            oracle.mode == "oracleOnly"
1661                && oracle.divergence_count == 0
1662                && oracle.all_legacy_declaration_values_preserved
1663        })
1664    }
1665
1666    pub fn may_consume_native_product_output(&self) -> bool {
1667        self.declares_native_product_output() && self.oracle_allows_native_product_output()
1668    }
1669
1670    // NOTE: the "retained oracle" here is the retained product-output string (`evaluated_css`),
1671    // which in the production rail is itself native-derived — so this is a byte-equality
1672    // self-consistency check between two native-derived strings, not a comparison to an
1673    // independent external evaluator.
1674    pub fn native_output_matches_retained_oracle(&self, native_output: &str) -> bool {
1675        self.oracle
1676            .as_ref()
1677            .is_some_and(|_| native_output == self.evaluated_css)
1678    }
1679}
1680
1681#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1682#[serde(rename_all = "camelCase")]
1683pub struct TransformModuleEvaluationNativeReplacementV0 {
1684    pub name: String,
1685    pub start: usize,
1686    pub end: usize,
1687    pub text: String,
1688    #[serde(default, skip_serializing_if = "Option::is_none")]
1689    pub rendered_value: Option<String>,
1690    pub abstract_value: AbstractCssValueV0,
1691    pub abstract_value_kind: String,
1692}
1693
1694#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1695#[serde(rename_all = "camelCase")]
1696pub struct TransformModuleEvaluationNativeEditV0 {
1697    pub start: usize,
1698    pub end: usize,
1699    pub replacement: String,
1700    pub edit_kind: String,
1701    #[serde(default, skip_serializing_if = "Option::is_none")]
1702    pub abstract_value: Option<AbstractCssValueV0>,
1703    #[serde(default, skip_serializing_if = "Option::is_none")]
1704    pub abstract_value_kind: Option<String>,
1705}
1706
1707#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
1708#[serde(default, rename_all = "camelCase")]
1709pub struct TransformModuleEvaluationOracleV0 {
1710    pub mode: String,
1711    pub product_output_source: String,
1712    pub legacy_declaration_value_count: usize,
1713    pub abstract_value_count: usize,
1714    pub exact_value_count: usize,
1715    pub raw_value_count: usize,
1716    pub bottom_value_count: usize,
1717    pub top_value_count: usize,
1718    pub divergence_count: usize,
1719    pub all_legacy_declaration_values_preserved: bool,
1720    pub native_replacement_count: usize,
1721    pub native_replacement_legacy_reflection_count: usize,
1722    pub native_replacement_legacy_unreflected_count: usize,
1723    pub native_value_reference_count: usize,
1724    pub native_resolved_value_count: usize,
1725    pub native_raw_value_count: usize,
1726    pub native_top_value_count: usize,
1727    pub native_cycle_count: usize,
1728    pub native_fuel_exhausted_count: usize,
1729    pub native_unresolved_reference_count: usize,
1730    pub native_unsupported_dynamic_count: usize,
1731}
1732
1733#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1734#[serde(rename_all = "camelCase")]
1735pub struct TransformImportInlineV0 {
1736    pub import_source: String,
1737    pub replacement_css: String,
1738}
1739
1740#[derive(Debug, Clone, PartialEq, Eq)]
1741pub struct TransformLessInlineLiteralPlaceholderV0 {
1742    pub placeholder: String,
1743    pub literal_css: String,
1744}
1745
1746#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1747#[serde(rename_all = "camelCase")]
1748pub struct TransformClassNameRewriteV0 {
1749    pub original_name: String,
1750    pub rewritten_name: String,
1751}
1752
1753/// Module-qualified CSS Modules rewrite input.
1754///
1755/// Consumers that need different rewrites for identical class spellings in
1756/// different modules use this carrier instead of flattening those rewrites
1757/// into [`TransformExecutionContextV0::class_name_rewrites`]. The module key is
1758/// compared before the canonical class-name key. Under an equal compound key,
1759/// the consumer-supplied record is the first witness and wins independently of
1760/// raw spelling or later presentation sorting.
1761#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1762#[serde(rename_all = "camelCase")]
1763#[non_exhaustive]
1764pub struct TransformModuleCssModuleContextV0 {
1765    pub module_instance: ModuleInstanceKeyV0,
1766    pub class_name_rewrites: Vec<TransformClassNameRewriteV0>,
1767    pub composes_resolutions: Vec<TransformCssModuleComposesResolutionV0>,
1768}
1769
1770impl TransformModuleCssModuleContextV0 {
1771    pub fn new(module_instance: ModuleInstanceKeyV0) -> Self {
1772        Self {
1773            module_instance,
1774            class_name_rewrites: Vec::new(),
1775            composes_resolutions: Vec::new(),
1776        }
1777    }
1778
1779    pub fn with_class_name_rewrites(
1780        mut self,
1781        class_name_rewrites: Vec<TransformClassNameRewriteV0>,
1782    ) -> Self {
1783        self.class_name_rewrites = class_name_rewrites;
1784        self
1785    }
1786
1787    pub fn with_composes_resolutions(
1788        mut self,
1789        composes_resolutions: Vec<TransformCssModuleComposesResolutionV0>,
1790    ) -> Self {
1791        self.composes_resolutions = composes_resolutions;
1792        self
1793    }
1794}
1795
1796#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1797#[serde(rename_all = "camelCase")]
1798pub struct TransformCssModuleComposesResolutionV0 {
1799    pub local_class_name: String,
1800    pub exported_class_names: Vec<String>,
1801}
1802
1803#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1804#[serde(rename_all = "camelCase")]
1805pub struct TransformCssModuleValueResolutionV0 {
1806    pub local_name: String,
1807    pub resolved_value: String,
1808}
1809
1810#[derive(Debug, Clone, Deserialize, Serialize)]
1811#[serde(rename_all = "camelCase")]
1812pub struct TransformDesignTokenRouteV0 {
1813    pub token_name: AuthoredPropertyTextV0,
1814    pub routed_value: String,
1815}
1816
1817impl PartialEq for TransformDesignTokenRouteV0 {
1818    fn eq(&self, other: &Self) -> bool {
1819        self.token_name.to_custom_key() == other.token_name.to_custom_key()
1820            && self.routed_value == other.routed_value
1821    }
1822}
1823
1824impl Eq for TransformDesignTokenRouteV0 {}
1825
1826#[cfg(test)]
1827mod authored_property_identity_tests {
1828    use super::*;
1829
1830    #[test]
1831    fn winner_equality_affected_pair_identity_uses_sealed_property_keys() {
1832        let pair = |property: &str| TransformWinnerEqualityAffectedPairV0 {
1833            element_signature: ElementSignature::concrete(
1834                None::<String>,
1835                None::<String>,
1836                ["button"],
1837            ),
1838            property: AuthoredPropertyTextV0::new(property),
1839        };
1840
1841        assert_eq!(pair("COLOR"), pair(r"C\4f LOR"));
1842        assert_ne!(pair("--foo"), pair("--FOO"));
1843    }
1844
1845    #[test]
1846    fn execution_context_identity_uses_custom_property_keys() {
1847        let context = |property: &str| TransformExecutionContextV0 {
1848            reachable_custom_property_names: vec![AuthoredPropertyTextV0::new(property)],
1849            ..TransformExecutionContextV0::default()
1850        };
1851
1852        assert_eq!(context(r"--f\6f o"), context("--foo"));
1853        assert_ne!(context("--foo"), context("--FOO"));
1854    }
1855
1856    #[test]
1857    fn cascade_environment_declaration_identity_uses_sealed_property_keys() {
1858        let declaration = |property: &str| TransformCascadeEnvironmentDeclarationV0 {
1859            declaration_id: "declaration".to_string(),
1860            selector: ".button".to_string(),
1861            property: AuthoredPropertyTextV0::new(property),
1862            value: "red".to_string(),
1863            origin: CascadeOriginV0::Author,
1864            important: false,
1865            layer_rank: None,
1866            scope_proximity: None,
1867            source_order: 0,
1868        };
1869
1870        assert_eq!(declaration("COLOR"), declaration(r"C\4f LOR"));
1871        assert_ne!(declaration("--foo"), declaration("--FOO"));
1872    }
1873
1874    #[test]
1875    fn design_token_route_identity_uses_custom_property_keys() {
1876        let route = |name: &str| TransformDesignTokenRouteV0 {
1877            token_name: AuthoredPropertyTextV0::new(name),
1878            routed_value: "red".to_string(),
1879        };
1880
1881        assert_eq!(route(r"--f\6f o"), route("--foo"));
1882        assert_ne!(route("--foo"), route("--FOO"));
1883    }
1884}
1885
1886#[cfg(test)]
1887mod evidence_graph_tests {
1888    use super::*;
1889    use omena_cascade::{
1890        CascadeKey, CascadeValue, LayerOrdinal, OpenWorldTieEvidence, Specificity,
1891        cascade_property, normalized_layer_rank,
1892    };
1893
1894    fn winner_equality_test_declaration(
1895        id: &str,
1896        value: &str,
1897        source_order: u32,
1898    ) -> CascadeDeclaration {
1899        CascadeDeclaration {
1900            id: id.to_string(),
1901            property: omena_syntax::ident::AuthoredPropertyTextV0::new("color"),
1902            property_key: omena_syntax::ident::PropertyNameV0::standard("color").canonical_key(),
1903            value: CascadeValue::Literal(value.to_string()),
1904            key: CascadeKey::new(
1905                CascadeLevel::AuthorNormal,
1906                normalized_layer_rank(false, LayerOrdinal::new(0)),
1907                0,
1908                Specificity::new(0, 1, 0),
1909                source_order,
1910            ),
1911            open_world_tie_evidence: OpenWorldTieEvidence::NONE,
1912            specificity_exactness: omena_cascade::SpecificityExactnessV0::Exact,
1913        }
1914    }
1915
1916    #[test]
1917    fn winner_equality_witness_consumes_the_cascade_authority_outcome() -> Result<(), String> {
1918        let outcome = cascade_property(
1919            [
1920                winner_equality_test_declaration("earlier", "red", 0),
1921                winner_equality_test_declaration("later", "blue", 1),
1922            ],
1923            "color",
1924        );
1925        let witness = TransformWinnerEqualityWitnessV0::from_cascade_outcome(&outcome)
1926            .ok_or_else(|| "the closed cascade should have a definite winner".to_string())?;
1927
1928        assert_eq!(witness.winner.id, "later");
1929        assert_eq!(
1930            witness.proof,
1931            CascadeProof::from_declaration(&witness.winner)
1932        );
1933        Ok(())
1934    }
1935
1936    #[test]
1937    fn winner_equality_witness_stays_absent_for_non_definite_outcomes() {
1938        assert!(
1939            TransformWinnerEqualityWitnessV0::from_cascade_outcome(&CascadeOutcome::Top).is_none()
1940        );
1941        assert!(
1942            TransformWinnerEqualityWitnessV0::from_cascade_outcome(&CascadeOutcome::Inherit)
1943                .is_none()
1944        );
1945    }
1946
1947    #[test]
1948    fn winner_equality_trust_records_name_covered_axes() -> Result<(), serde_json::Error> {
1949        let tier = TransformSemanticGuaranteeTierV0::WinnerEqualityObserved {
1950            axes: vec![
1951                TransformWinnerEqualityAxisV0::CascadeLevel,
1952                TransformWinnerEqualityAxisV0::LayerRank,
1953            ],
1954        };
1955
1956        assert_eq!(
1957            serde_json::to_value(tier)?,
1958            serde_json::json!({
1959                "kind": "winnerEqualityObserved",
1960                "axes": ["cascadeLevel", "layerRank"]
1961            })
1962        );
1963        Ok(())
1964    }
1965
1966    #[test]
1967    fn winner_equality_absence_names_the_undriven_level() -> Result<(), serde_json::Error> {
1968        let tier = TransformSemanticGuaranteeTierV0::Absent {
1969            reasons: vec![TransformWinnerEqualityAbsenceV0 {
1970                axis: TransformWinnerEqualityAxisV0::CascadeLevel,
1971                reason: TransformWinnerEqualityAbsenceReasonV0::DriverUnavailable {
1972                    level: Some(CascadeLevel::Animation),
1973                },
1974            }],
1975        };
1976
1977        assert_eq!(
1978            serde_json::to_value(tier)?,
1979            serde_json::json!({
1980                "kind": "absent",
1981                "reasons": [{
1982                    "axis": "cascadeLevel",
1983                    "reason": {
1984                        "kind": "driverUnavailable",
1985                        "level": "animation"
1986                    }
1987                }]
1988            })
1989        );
1990        Ok(())
1991    }
1992
1993    #[test]
1994    fn transform_outcome_evidence_graph_preserves_public_shape() -> Result<(), serde_json::Error> {
1995        let outcome = TransformPassExecutionOutcomeV0 {
1996            pass_id: "number-compression",
1997            status: TransformPassRuntimeStatus::Applied,
1998            input_byte_len: 32,
1999            output_byte_len: 28,
2000            mutation_count: 1,
2001            provenance_preserved: true,
2002            detail: "fixture pass",
2003        };
2004
2005        let before = serde_json::to_value(&outcome)?;
2006        let node = outcome.evidence_node_seed();
2007        let graph = build_evidence_graph_from_edges_v0([node], [outcome.evidence_demand_edge()])
2008            .map_err(|_| serde::ser::Error::custom("outcome edge must target its node"))?;
2009        let after = serde_json::to_value(&outcome)?;
2010
2011        assert_eq!(before, after);
2012        assert_eq!(graph.nodes.len(), 1);
2013        assert_eq!(graph.nodes[0].key.input_identity, "number-compression");
2014        assert_eq!(graph.nodes[0].guarantee, GuaranteeKindV0::Floor);
2015        assert!(
2016            graph.nodes[0]
2017                .provenance
2018                .iter()
2019                .any(|item| item == "mutationCount:1")
2020        );
2021        Ok(())
2022    }
2023
2024    #[test]
2025    fn transform_derivation_forest_evidence_graph_preserves_public_shape()
2026    -> Result<(), serde_json::Error> {
2027        let forest = TransformProvenanceDerivationForestV0 {
2028            schema_version: "0",
2029            product: "omena-transform-passes.provenance-derivation-forest",
2030            root_count: 1,
2031            node_count: 1,
2032            nodes: vec![TransformProvenanceDerivationNodeV0 {
2033                node_index: 0,
2034                parent_index: None,
2035                pass_id: "comment-strip",
2036                status: TransformPassRuntimeStatus::Applied,
2037                input_byte_len: 48,
2038                output_byte_len: 36,
2039                source_span_start: 0,
2040                source_span_end: 12,
2041                generated_span_start: 0,
2042                generated_span_end: 0,
2043                mutation_spans: Vec::new(),
2044                mutation_count: 1,
2045                provenance_preserved: true,
2046                detail: "fixture derivation",
2047            }],
2048        };
2049
2050        let before = serde_json::to_value(&forest)?;
2051        let graph = forest
2052            .evidence_graph()
2053            .map_err(|_| serde::ser::Error::custom("forest edge must target its node"))?;
2054        let after = serde_json::to_value(&forest)?;
2055
2056        assert_eq!(before, after);
2057        assert_eq!(graph.nodes.len(), 1);
2058        assert_eq!(graph.nodes[0].key.input_identity, "comment-strip#0");
2059        assert_eq!(graph.nodes[0].guarantee, GuaranteeKindV0::Floor);
2060        Ok(())
2061    }
2062}