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, SupportsTargetCapabilityV0,
13};
14use omena_cascade_proof::{
15    CanonicalSmtInputV0, DischargeLedgerLookupStatusV0, DischargeLedgerLookupV0,
16    DischargeLedgerVerdictV0,
17};
18use omena_evidence_graph::{
19    EvidenceDemandEdgeV0, EvidenceGraphBuildErrorV0, EvidenceGraphV0, EvidenceNodeKeyV0,
20    EvidenceNodeSeedV0, GuaranteeFamilyV0, GuaranteeKindV0, build_evidence_graph_from_edges_v0,
21};
22use omena_incremental::{IncrementalComputationPlanV0, IncrementalSnapshotV0};
23use omena_parser::ModuleInstanceKeyV0;
24use omena_transform_cst::{
25    StableNodeKeyV0, TransformBuildProfileV0, TransformDagEdgeV0, TransformPassContractV0,
26    TransformPassDescriptorV0, TransformPassKind, TransformStrictPolicyDescriptorV0,
27    strict_policy_descriptor_for_profile,
28};
29use serde::{Deserialize, Serialize};
30use serde_json::Value;
31
32const TRANSFORM_PASS_OUTCOME_EVIDENCE_QUERY_V0: &str =
33    "omena-transform-passes.transform-pass-execution-outcome";
34const TRANSFORM_PROVENANCE_NODE_EVIDENCE_QUERY_V0: &str =
35    "omena-transform-passes.provenance-derivation-node";
36const TRANSFORM_EVIDENCE_EDGE_KIND_V0: &str = "transform-evidence";
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
39#[serde(rename_all = "camelCase")]
40pub enum TransformPassExecutionStatus {
41    RegistryAndPlannerReady,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
45#[serde(rename_all = "camelCase")]
46pub enum TransformPassDispatchKindV0 {
47    TextLocalSliceRewrite,
48    StructuralIrTransaction,
49    ModuleEvaluationHandler,
50    EmissionBoundary,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
54#[serde(rename_all = "camelCase")]
55pub struct TransformPassRegistryEntryV0 {
56    pub contract: TransformPassContractV0,
57    pub descriptor: TransformPassDescriptorV0,
58    pub module_family: &'static str,
59    pub query_family: &'static str,
60    pub dispatch_kind: TransformPassDispatchKindV0,
61    pub execution_status: TransformPassExecutionStatus,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
65#[serde(rename_all = "camelCase")]
66pub struct TransformPassRegistryV0 {
67    pub schema_version: &'static str,
68    pub product: &'static str,
69    pub entries: Vec<TransformPassRegistryEntryV0>,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
73#[serde(rename_all = "camelCase")]
74pub struct TransformPassesBoundarySummaryV0 {
75    pub schema_version: &'static str,
76    pub product: &'static str,
77    pub registry_entries: Vec<TransformPassRegistryEntryV0>,
78    pub dag_edges: Vec<TransformDagEdgeV0>,
79    pub pass_count: usize,
80    pub full_catalog_registered: bool,
81    pub semantic_aware_pass_count: usize,
82    pub cascade_aware_pass_count: usize,
83    pub structural_pass_count: usize,
84    pub text_local_pass_count: usize,
85    pub module_evaluation_pass_count: usize,
86    pub planner_enforces_dag_edges: bool,
87    pub planner_uses_pass_descriptors: bool,
88    pub ordinal_has_execution_semantics: bool,
89    pub execution_runtime_ready: bool,
90    pub incremental_execution_runtime_ready: bool,
91    pub module_evaluation_native_output_marker: &'static str,
92    pub module_evaluation_requires_native_product_output: bool,
93    pub module_evaluation_requires_oracle_readiness: bool,
94    pub module_evaluation_legacy_output_is_oracle_only: bool,
95    pub module_evaluation_preserves_source_without_native_output: bool,
96    pub implemented_mutation_pass_ids: Vec<&'static str>,
97    pub next_surfaces: Vec<&'static str>,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
101#[serde(rename_all = "camelCase")]
102pub struct TransformPassPlanV0 {
103    pub schema_version: &'static str,
104    pub product: &'static str,
105    pub build_profile: TransformBuildProfileV0,
106    pub requested_pass_ids: Vec<&'static str>,
107    pub ordered_pass_ids: Vec<&'static str>,
108    pub satisfied_dag_edge_count: usize,
109    pub violated_dag_edge_count: usize,
110    pub all_requested_registered: bool,
111    pub conflicting_unordered_pass_pairs: Vec<TransformPlanPassConflictV0>,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
115#[serde(rename_all = "camelCase")]
116pub struct TransformPlanPassConflictV0 {
117    pub pass_a: &'static str,
118    pub pass_b: &'static str,
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
122#[serde(rename_all = "camelCase")]
123pub struct TransformStructuralIrShadowFieldReportV0 {
124    pub field: &'static str,
125    pub string_path_values: Vec<String>,
126    pub ir_path_values: Vec<String>,
127    pub typed_path_values: Vec<String>,
128    pub matches: bool,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
132#[serde(rename_all = "camelCase")]
133pub struct TransformStructuralIrShadowFixtureReportV0 {
134    pub schema_version: &'static str,
135    pub product: &'static str,
136    pub fixture: String,
137    pub pass_id: &'static str,
138    pub dialect: &'static str,
139    pub string_path_mutation_count: Option<usize>,
140    pub ir_path_mutation_count: Option<usize>,
141    pub typed_path_mutation_count: Option<usize>,
142    pub ir_path_transaction_commit_count: Option<u64>,
143    pub typed_payload_projections_consumed: usize,
144    pub typed_payload_memo_hits: usize,
145    pub fields: Vec<TransformStructuralIrShadowFieldReportV0>,
146    pub all_fields_match: bool,
147    pub all_typed_path_fields_match: bool,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
151#[serde(rename_all = "camelCase")]
152pub struct TransformStructuralIrShadowEquivalenceReportV0 {
153    pub schema_version: &'static str,
154    pub product: &'static str,
155    pub fixture_count: usize,
156    pub compared_pass_ids: Vec<&'static str>,
157    pub compared_fields: Vec<&'static str>,
158    pub reports: Vec<TransformStructuralIrShadowFixtureReportV0>,
159    pub all_fields_match: bool,
160    pub all_typed_path_fields_match: bool,
161    pub typed_payload_projections_consumed: usize,
162    pub typed_payload_memo_hits: usize,
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
166#[serde(rename_all = "camelCase")]
167pub enum TransformPassRuntimeStatus {
168    Applied,
169    NoChange,
170    PlannedOnly,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
174#[serde(rename_all = "camelCase")]
175pub struct TransformPassExecutionOutcomeV0 {
176    pub pass_id: &'static str,
177    pub status: TransformPassRuntimeStatus,
178    pub input_byte_len: usize,
179    pub output_byte_len: usize,
180    pub mutation_count: usize,
181    pub provenance_preserved: bool,
182    pub detail: &'static str,
183}
184
185impl TransformPassExecutionOutcomeV0 {
186    pub fn evidence_node_key(&self) -> EvidenceNodeKeyV0 {
187        EvidenceNodeKeyV0::new(TRANSFORM_PASS_OUTCOME_EVIDENCE_QUERY_V0, self.pass_id)
188    }
189
190    pub fn evidence_node_seed(&self) -> EvidenceNodeSeedV0 {
191        EvidenceNodeSeedV0::new(
192            self.evidence_node_key(),
193            vec![
194                ["pass:", self.pass_id].concat(),
195                ["detail:", self.detail].concat(),
196                ["mutationCount:", self.mutation_count.to_string().as_str()].concat(),
197                [
198                    "provenancePreserved:",
199                    self.provenance_preserved.to_string().as_str(),
200                ]
201                .concat(),
202            ],
203            GuaranteeKindV0::for_label_less_family(),
204        )
205    }
206
207    pub fn evidence_demand_edge(&self) -> EvidenceDemandEdgeV0 {
208        EvidenceDemandEdgeV0::new(
209            TRANSFORM_PASS_OUTCOME_EVIDENCE_QUERY_V0,
210            self.evidence_node_key(),
211            TRANSFORM_EVIDENCE_EDGE_KIND_V0,
212        )
213    }
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
217#[serde(rename_all = "camelCase")]
218pub enum TransformEvaluationProfileV0 {
219    Scss,
220    Less,
221}
222
223#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
224#[serde(tag = "kind", rename_all = "camelCase")]
225pub enum TransformPreconditionV0 {
226    EvaluatorOutput {
227        profile: TransformEvaluationProfileV0,
228    },
229    ResolvedImportReplacements,
230    CssModulesComposesResolution,
231    DesignTokenRoutes,
232    SelectorIdentity,
233    ClosedStyleWorldBundle,
234    ClosedWorldBundle,
235}
236
237#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
238#[serde(tag = "kind", rename_all = "camelCase")]
239pub enum TransformNoChangeReasonV0 {
240    NoMutation,
241    EmissionBoundary,
242    ProfileNotApplicable {
243        profile: TransformEvaluationProfileV0,
244    },
245    NoMatchingSelectorRewrite,
246    DialectNotApplicable,
247}
248
249#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
250#[serde(
251    tag = "kind",
252    rename_all = "camelCase",
253    rename_all_fields = "camelCase"
254)]
255pub enum TransformBlockedReasonV0 {
256    MissingPrecondition {
257        precondition: TransformPreconditionV0,
258    },
259    PrecisionBelowFloor {
260        required: FactPrecision,
261        observed: FactPrecision,
262    },
263    DischargeMissing {
264        lookup_status: Option<DischargeLedgerLookupStatusV0>,
265        verdict: Option<DischargeLedgerVerdictV0>,
266    },
267    StrictVerification {
268        reasons: Vec<TransformStrictPolicyReasonV0>,
269    },
270    PassImplementation,
271}
272
273#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
274#[serde(tag = "kind", rename_all = "camelCase")]
275pub enum TransformRejectionReasonV0 {
276    IrTransaction {
277        pass: TransformPassKind,
278    },
279    SemanticPreservation,
280    StrictVerification {
281        reasons: Vec<TransformStrictPolicyReasonV0>,
282    },
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
286#[serde(
287    tag = "kind",
288    rename_all = "camelCase",
289    rename_all_fields = "camelCase"
290)]
291pub enum TransformStructuralDecisionClassV0 {
292    FactConsuming { required_precision: FactPrecision },
293    StaticExact,
294    ObligationDischarge,
295    NonRemovalRewrite,
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
299#[serde(rename_all = "camelCase")]
300pub struct TransformStructuralDecisionPolicyV0 {
301    pub pass: TransformPassKind,
302    pub class: TransformStructuralDecisionClassV0,
303    pub reason: &'static str,
304}
305
306impl TransformStructuralDecisionPolicyV0 {
307    pub const fn new(
308        pass: TransformPassKind,
309        class: TransformStructuralDecisionClassV0,
310        reason: &'static str,
311    ) -> Self {
312        Self {
313            pass,
314            class,
315            reason,
316        }
317    }
318
319    pub const fn required_precision(self) -> Option<FactPrecision> {
320        match self.class {
321            TransformStructuralDecisionClassV0::FactConsuming { required_precision } => {
322                Some(required_precision)
323            }
324            TransformStructuralDecisionClassV0::StaticExact
325            | TransformStructuralDecisionClassV0::ObligationDischarge
326            | TransformStructuralDecisionClassV0::NonRemovalRewrite => None,
327        }
328    }
329}
330
331pub const TRANSFORM_STRUCTURAL_DECISION_POLICIES_V0: &[TransformStructuralDecisionPolicyV0] = &[
332    TransformStructuralDecisionPolicyV0::new(
333        TransformPassKind::ImportInline,
334        TransformStructuralDecisionClassV0::NonRemovalRewrite,
335        "materializes explicitly resolved imports without reachability pruning",
336    ),
337    TransformStructuralDecisionPolicyV0::new(
338        TransformPassKind::ResolveCssModulesComposes,
339        TransformStructuralDecisionClassV0::NonRemovalRewrite,
340        "materializes explicit CSS Modules composition resolution",
341    ),
342    TransformStructuralDecisionPolicyV0::new(
343        TransformPassKind::DesignTokenRouting,
344        TransformStructuralDecisionClassV0::NonRemovalRewrite,
345        "rewrites values through explicit design-token routes",
346    ),
347    TransformStructuralDecisionPolicyV0::new(
348        TransformPassKind::HashCssModuleClassNames,
349        TransformStructuralDecisionClassV0::NonRemovalRewrite,
350        "rewrites selectors through an explicit identity map",
351    ),
352    TransformStructuralDecisionPolicyV0::new(
353        TransformPassKind::RuleDeduplication,
354        TransformStructuralDecisionClassV0::StaticExact,
355        "removes only statically equivalent duplicate rules",
356    ),
357    TransformStructuralDecisionPolicyV0::new(
358        TransformPassKind::RuleMerging,
359        TransformStructuralDecisionClassV0::NonRemovalRewrite,
360        "combines adjacent declarations without reachability pruning",
361    ),
362    TransformStructuralDecisionPolicyV0::new(
363        TransformPassKind::SelectorMerging,
364        TransformStructuralDecisionClassV0::NonRemovalRewrite,
365        "combines equivalent selector blocks without reachability pruning",
366    ),
367    TransformStructuralDecisionPolicyV0::new(
368        TransformPassKind::NestingUnwrap,
369        TransformStructuralDecisionClassV0::NonRemovalRewrite,
370        "expands nested selectors without reachability pruning",
371    ),
372    TransformStructuralDecisionPolicyV0::new(
373        TransformPassKind::ScopeFlatten,
374        TransformStructuralDecisionClassV0::ObligationDischarge,
375        "requires accepted scope-flatten obligations",
376    ),
377    TransformStructuralDecisionPolicyV0::new(
378        TransformPassKind::LayerFlatten,
379        TransformStructuralDecisionClassV0::ObligationDischarge,
380        "requires accepted layer-flatten obligations",
381    ),
382    TransformStructuralDecisionPolicyV0::new(
383        TransformPassKind::SupportsStaticEval,
384        TransformStructuralDecisionClassV0::StaticExact,
385        "removes only statically decided supports branches",
386    ),
387    TransformStructuralDecisionPolicyV0::new(
388        TransformPassKind::MediaStaticEval,
389        TransformStructuralDecisionClassV0::StaticExact,
390        "removes only statically unsatisfiable media branches",
391    ),
392    TransformStructuralDecisionPolicyV0::new(
393        TransformPassKind::ContainerStaticEval,
394        TransformStructuralDecisionClassV0::StaticExact,
395        "removes only statically unsatisfiable container branches",
396    ),
397    TransformStructuralDecisionPolicyV0::new(
398        TransformPassKind::NativeCssStaticEval,
399        TransformStructuralDecisionClassV0::StaticExact,
400        "folds only statically evaluable native CSS expressions",
401    ),
402    TransformStructuralDecisionPolicyV0::new(
403        TransformPassKind::DeadMediaBranchRemoval,
404        TransformStructuralDecisionClassV0::StaticExact,
405        "removes only media branches selected by explicit static policy",
406    ),
407    TransformStructuralDecisionPolicyV0::new(
408        TransformPassKind::DeadSupportsBranchRemoval,
409        TransformStructuralDecisionClassV0::StaticExact,
410        "removes only statically decided supports branches",
411    ),
412    TransformStructuralDecisionPolicyV0::new(
413        TransformPassKind::TreeShakeClass,
414        TransformStructuralDecisionClassV0::FactConsuming {
415            required_precision: FactPrecision::Conservative,
416        },
417        "removes class rules only from a closed-world reachability over-approximation",
418    ),
419    TransformStructuralDecisionPolicyV0::new(
420        TransformPassKind::TreeShakeKeyframes,
421        TransformStructuralDecisionClassV0::FactConsuming {
422            required_precision: FactPrecision::Conservative,
423        },
424        "removes keyframes only from a closed-world reachability over-approximation",
425    ),
426    TransformStructuralDecisionPolicyV0::new(
427        TransformPassKind::TreeShakeValue,
428        TransformStructuralDecisionClassV0::FactConsuming {
429            required_precision: FactPrecision::Conservative,
430        },
431        "removes CSS Modules values only from a closed-world reachability over-approximation",
432    ),
433    TransformStructuralDecisionPolicyV0::new(
434        TransformPassKind::TreeShakeCustomProperty,
435        TransformStructuralDecisionClassV0::FactConsuming {
436            required_precision: FactPrecision::Conservative,
437        },
438        "removes custom properties only from a closed-world reachability over-approximation",
439    ),
440    TransformStructuralDecisionPolicyV0::new(
441        TransformPassKind::EmptyRuleRemoval,
442        TransformStructuralDecisionClassV0::StaticExact,
443        "removes only structurally empty rules",
444    ),
445];
446
447pub fn transform_structural_decision_policy(
448    pass: TransformPassKind,
449) -> Option<&'static TransformStructuralDecisionPolicyV0> {
450    TRANSFORM_STRUCTURAL_DECISION_POLICIES_V0
451        .iter()
452        .find(|policy| policy.pass == pass)
453}
454
455#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
456#[serde(tag = "kind", rename_all = "camelCase")]
457pub enum RollbackScopeV0 {
458    RejectPreservedInput,
459    InversePatch,
460    CommittedIrrecoverable,
461}
462
463#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
464#[serde(rename_all = "camelCase")]
465pub struct RollbackReceiptV0 {
466    pub pass_id: String,
467    pub attempted_mutation_count: Option<usize>,
468    pub input_content_signature: String,
469    #[serde(skip_serializing_if = "Option::is_none")]
470    pub output_preserved_content_signature: Option<String>,
471    pub restorable: RollbackScopeV0,
472}
473
474#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
475#[serde(rename_all = "camelCase")]
476pub struct TransformDischargeEvidenceV0 {
477    pub evidence_node_key: EvidenceNodeKeyV0,
478    pub guarantee_family: GuaranteeFamilyV0,
479    pub ledger_cell_key: String,
480    pub boundedness_kind: String,
481}
482
483/// Cascade dimensions covered by an observed winner-equality comparison.
484///
485/// Coverage is explicit so consumers do not mistake a partial observation for
486/// a guarantee over cascade dimensions that have no production driver yet.
487#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
488#[serde(rename_all = "camelCase")]
489pub enum TransformWinnerEqualityAxisV0 {
490    CascadeLevel,
491    LayerRank,
492    ScopeProximity,
493    Specificity,
494    SourceOrder,
495}
496
497/// Why a winner-equality observation could not cover one cascade dimension.
498#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
499#[serde(
500    tag = "kind",
501    rename_all = "camelCase",
502    rename_all_fields = "camelCase"
503)]
504pub enum TransformWinnerEqualityAbsenceReasonV0 {
505    DriverUnavailable { level: Option<CascadeLevel> },
506    AffectedPairUnavailable,
507    SpecificityInexact,
508    WinnerNotDefinite,
509    WinnerChanged,
510}
511
512/// A typed precision boundary for a missing winner-equality observation.
513#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
514#[serde(rename_all = "camelCase")]
515pub struct TransformWinnerEqualityAbsenceV0 {
516    pub axis: TransformWinnerEqualityAxisV0,
517    pub reason: TransformWinnerEqualityAbsenceReasonV0,
518}
519
520/// The semantic location whose cascade winner is compared across a transform.
521#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
522#[serde(rename_all = "camelCase")]
523pub struct TransformWinnerEqualityAffectedPairV0 {
524    pub element_signature: ElementSignature,
525    pub property: String,
526}
527
528/// A definite winner and the proof emitted by the cascade authority.
529///
530/// Keeping the authority-owned types here prevents transform code from
531/// reconstructing winner order or proof fields independently.
532#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
533#[serde(rename_all = "camelCase")]
534pub struct TransformWinnerEqualityWitnessV0 {
535    pub winner: CascadeDeclaration,
536    pub proof: CascadeProof,
537}
538
539impl TransformWinnerEqualityWitnessV0 {
540    pub fn from_cascade_outcome(outcome: &CascadeOutcome) -> Option<Self> {
541        match outcome {
542            CascadeOutcome::Definite { winner, proof, .. } => Some(Self {
543                winner: winner.clone(),
544                proof: proof.as_ref().clone(),
545            }),
546            CascadeOutcome::RankedSet(_) | CascadeOutcome::Inherit | CascadeOutcome::Top => None,
547        }
548    }
549}
550
551/// Result of comparing authority-produced cascade witnesses for one affected pair.
552#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
553#[serde(
554    tag = "kind",
555    rename_all = "camelCase",
556    rename_all_fields = "camelCase"
557)]
558pub enum TransformWinnerEqualityObservationV0 {
559    ObservedEqual {
560        axes: Vec<TransformWinnerEqualityAxisV0>,
561        input: TransformWinnerEqualityWitnessV0,
562        output: TransformWinnerEqualityWitnessV0,
563    },
564    ObservedDifferent {
565        axes: Vec<TransformWinnerEqualityAxisV0>,
566        input: TransformWinnerEqualityWitnessV0,
567        output: TransformWinnerEqualityWitnessV0,
568    },
569    Absent {
570        reasons: Vec<TransformWinnerEqualityAbsenceV0>,
571    },
572}
573
574/// A cascade-winner comparison requested for one admitted transform mutation.
575#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
576#[serde(rename_all = "camelCase")]
577pub struct TransformWinnerEqualityObligationV0 {
578    pub pass_id: &'static str,
579    pub affected_pair: TransformWinnerEqualityAffectedPairV0,
580    pub observation: TransformWinnerEqualityObservationV0,
581}
582
583#[derive(Debug, Clone, Default, PartialEq, Eq)]
584pub struct TransformExecutionPolicyV0 {
585    pub strict_policy: Option<TransformStrictPolicyDescriptorV0>,
586}
587
588impl TransformExecutionPolicyV0 {
589    pub fn for_profile(profile_id: &str) -> Option<Self> {
590        strict_policy_descriptor_for_profile(profile_id).map(|strict_policy| Self {
591            strict_policy: Some(strict_policy),
592        })
593    }
594}
595
596#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
597#[serde(
598    tag = "kind",
599    rename_all = "camelCase",
600    rename_all_fields = "camelCase"
601)]
602pub enum TransformStrictPolicyReasonV0 {
603    RequiredAxisUnavailable {
604        axis: TransformWinnerEqualityAxisV0,
605    },
606    CascadeEnvironmentUnavailable,
607    WinnerChanged {
608        axes: Vec<TransformWinnerEqualityAxisV0>,
609    },
610    ObservationUnavailable {
611        reasons: Vec<TransformWinnerEqualityAbsenceV0>,
612    },
613    UnknownPass,
614    ClosedWorldEvidenceUnavailable,
615    DecisionCoverageIncomplete,
616}
617
618#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
619#[serde(rename_all = "camelCase")]
620pub struct TransformStrictPolicyEventV0 {
621    pub pass_id: String,
622    pub reasons: Vec<TransformStrictPolicyReasonV0>,
623}
624
625#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
626#[serde(rename_all = "camelCase")]
627pub struct TransformStrictPolicySummaryV0 {
628    pub profile_id: Option<String>,
629    pub refused_count: usize,
630    pub rolled_back_count: usize,
631    pub refusal_reasons: Vec<TransformStrictPolicyEventV0>,
632    pub rollback_reasons: Vec<TransformStrictPolicyEventV0>,
633}
634
635impl TransformStrictPolicySummaryV0 {
636    pub fn for_profile(profile_id: &str) -> Self {
637        Self {
638            profile_id: Some(profile_id.to_string()),
639            ..Self::default()
640        }
641    }
642
643    pub fn record_refusal(
644        &mut self,
645        pass_id: impl Into<String>,
646        reasons: Vec<TransformStrictPolicyReasonV0>,
647    ) {
648        self.refusal_reasons.push(TransformStrictPolicyEventV0 {
649            pass_id: pass_id.into(),
650            reasons,
651        });
652        self.refused_count = self.refusal_reasons.len();
653    }
654
655    pub fn record_rollback(
656        &mut self,
657        pass_id: impl Into<String>,
658        reasons: Vec<TransformStrictPolicyReasonV0>,
659    ) {
660        self.rollback_reasons.push(TransformStrictPolicyEventV0 {
661            pass_id: pass_id.into(),
662            reasons,
663        });
664        self.rolled_back_count = self.rollback_reasons.len();
665    }
666}
667
668/// Trust carried by an admitted transform decision.
669///
670/// This is descriptive evidence for default and other non-strict profiles. The
671/// enum itself never participates in admission. An opt-in strict profile may
672/// separately enforce the underlying typed obligations while leaving the base
673/// admission predicate unchanged.
674#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
675#[serde(
676    tag = "kind",
677    rename_all = "camelCase",
678    rename_all_fields = "camelCase"
679)]
680pub enum TransformSemanticGuaranteeTierV0 {
681    L0Observed,
682    WinnerEqualityObserved {
683        axes: Vec<TransformWinnerEqualityAxisV0>,
684    },
685    Absent {
686        reasons: Vec<TransformWinnerEqualityAbsenceV0>,
687    },
688}
689
690impl RollbackReceiptV0 {
691    pub fn preserves_rejected_input(&self) -> bool {
692        self.restorable == RollbackScopeV0::RejectPreservedInput
693            && self.output_preserved_content_signature.as_deref()
694                == Some(self.input_content_signature.as_str())
695    }
696
697    pub fn covers_inverse_patch(
698        &self,
699        inverse_patch_count: usize,
700        input_content_signature: &str,
701    ) -> bool {
702        self.restorable == RollbackScopeV0::InversePatch
703            && self.attempted_mutation_count == Some(inverse_patch_count)
704            && self.input_content_signature == input_content_signature
705            && self.output_preserved_content_signature.is_none()
706    }
707}
708
709#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
710#[serde(
711    tag = "kind",
712    rename_all = "camelCase",
713    rename_all_fields = "camelCase"
714)]
715pub enum TransformDecision {
716    Applied {
717        outcome: TransformPassExecutionOutcomeV0,
718        rollback_receipt: RollbackReceiptV0,
719        #[serde(skip_serializing_if = "Option::is_none")]
720        semantic_guarantee_tier: Option<TransformSemanticGuaranteeTierV0>,
721        #[serde(skip_serializing_if = "Vec::is_empty")]
722        discharge_evidence: Vec<TransformDischargeEvidenceV0>,
723    },
724    NoChange {
725        reason: TransformNoChangeReasonV0,
726        outcome: TransformPassExecutionOutcomeV0,
727    },
728    Blocked {
729        reason: TransformBlockedReasonV0,
730        outcome: TransformPassExecutionOutcomeV0,
731    },
732    Rejected {
733        reason: TransformRejectionReasonV0,
734        outcome: TransformPassExecutionOutcomeV0,
735        rollback_receipt: RollbackReceiptV0,
736    },
737}
738
739impl TransformDecision {
740    pub fn compatibility_outcome(&self) -> &TransformPassExecutionOutcomeV0 {
741        match self {
742            Self::Applied { outcome, .. }
743            | Self::NoChange { outcome, .. }
744            | Self::Blocked { outcome, .. }
745            | Self::Rejected { outcome, .. } => outcome,
746        }
747    }
748
749    pub fn into_compatibility_outcome(self) -> TransformPassExecutionOutcomeV0 {
750        match self {
751            Self::Applied { outcome, .. }
752            | Self::NoChange { outcome, .. }
753            | Self::Blocked { outcome, .. }
754            | Self::Rejected { outcome, .. } => outcome,
755        }
756    }
757
758    pub fn rollback_receipt(&self) -> Option<&RollbackReceiptV0> {
759        match self {
760            Self::Applied {
761                rollback_receipt, ..
762            }
763            | Self::Rejected {
764                rollback_receipt, ..
765            } => Some(rollback_receipt),
766            Self::NoChange { .. } | Self::Blocked { .. } => None,
767        }
768    }
769
770    pub fn semantic_guarantee_tier(&self) -> Option<&TransformSemanticGuaranteeTierV0> {
771        match self {
772            Self::Applied {
773                semantic_guarantee_tier,
774                ..
775            } => semantic_guarantee_tier.as_ref(),
776            Self::NoChange { .. } | Self::Blocked { .. } | Self::Rejected { .. } => None,
777        }
778    }
779}
780
781#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
782#[serde(rename_all = "camelCase")]
783pub struct TransformProvenanceDerivationForestV0 {
784    pub schema_version: &'static str,
785    pub product: &'static str,
786    pub root_count: usize,
787    pub node_count: usize,
788    pub nodes: Vec<TransformProvenanceDerivationNodeV0>,
789}
790
791impl TransformProvenanceDerivationForestV0 {
792    pub fn evidence_graph(&self) -> Result<EvidenceGraphV0, EvidenceGraphBuildErrorV0> {
793        build_evidence_graph_from_edges_v0(
794            self.nodes
795                .iter()
796                .map(TransformProvenanceDerivationNodeV0::evidence_node_seed),
797            self.nodes
798                .iter()
799                .map(TransformProvenanceDerivationNodeV0::evidence_demand_edge),
800        )
801    }
802}
803
804#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
805#[serde(rename_all = "camelCase")]
806pub struct TransformProvenanceDerivationNodeV0 {
807    pub node_index: usize,
808    pub parent_index: Option<usize>,
809    pub pass_id: &'static str,
810    pub status: TransformPassRuntimeStatus,
811    pub input_byte_len: usize,
812    pub output_byte_len: usize,
813    pub source_span_start: usize,
814    pub source_span_end: usize,
815    pub generated_span_start: usize,
816    pub generated_span_end: usize,
817    pub mutation_spans: Vec<TransformProvenanceMutationSpanV0>,
818    pub mutation_count: usize,
819    pub provenance_preserved: bool,
820    pub detail: &'static str,
821}
822
823impl TransformProvenanceDerivationNodeV0 {
824    pub fn evidence_node_key(&self) -> EvidenceNodeKeyV0 {
825        EvidenceNodeKeyV0::new(
826            TRANSFORM_PROVENANCE_NODE_EVIDENCE_QUERY_V0,
827            format!("{}#{}", self.pass_id, self.node_index),
828        )
829    }
830
831    pub fn evidence_node_seed(&self) -> EvidenceNodeSeedV0 {
832        EvidenceNodeSeedV0::new(
833            self.evidence_node_key(),
834            vec![
835                ["pass:", self.pass_id].concat(),
836                ["detail:", self.detail].concat(),
837                ["mutationCount:", self.mutation_count.to_string().as_str()].concat(),
838                [
839                    "provenancePreserved:",
840                    self.provenance_preserved.to_string().as_str(),
841                ]
842                .concat(),
843            ],
844            GuaranteeKindV0::for_label_less_family(),
845        )
846    }
847
848    pub fn evidence_demand_edge(&self) -> EvidenceDemandEdgeV0 {
849        EvidenceDemandEdgeV0::new(
850            TRANSFORM_PROVENANCE_NODE_EVIDENCE_QUERY_V0,
851            self.evidence_node_key(),
852            TRANSFORM_EVIDENCE_EDGE_KIND_V0,
853        )
854    }
855}
856
857#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
858#[serde(rename_all = "camelCase")]
859pub struct TransformProvenanceMutationSpanV0 {
860    pub source_span_start: usize,
861    pub source_span_end: usize,
862    pub generated_span_start: usize,
863    pub generated_span_end: usize,
864    #[serde(skip_serializing_if = "Option::is_none")]
865    pub node_key: Option<StableNodeKeyV0>,
866}
867
868/// Counts incremental lex-splice outcomes inside a transform execution.
869///
870/// A fallback is conservative: the cache declines to reuse token ranges and the
871/// next consumer re-lexes the generated source normally.
872#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
873#[serde(rename_all = "camelCase")]
874pub struct TransformLexCacheSpliceTelemetryV0 {
875    /// Number of generated token streams inserted through bounded splicing.
876    pub splice_hit_count: u64,
877    /// Number of active-cache attempts that intentionally fell back to full re-lex.
878    pub full_relex_fallback_count: u64,
879    /// Fallbacks caused by invalid or non-projectable mutation windows.
880    pub window_derivation_fallback_count: u64,
881    /// Fallbacks where the safe restart window covers the full generated output.
882    pub full_output_window_fallback_count: u64,
883    /// Fallbacks caused by token offset arithmetic or projection failure.
884    pub token_offset_fallback_count: u64,
885}
886
887/// Counts structural IR transaction outcomes that matter for String-currency
888/// retirement.
889#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
890#[serde(rename_all = "camelCase")]
891pub struct TransformStructuralIrTransactionTelemetryV0 {
892    pub transaction_commit_count: u64,
893}
894
895#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
896#[serde(rename_all = "camelCase")]
897pub enum TransformSemanticObservationKeyAxisV0 {
898    Selector,
899    Property,
900    Context,
901}
902
903#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
904#[serde(rename_all = "camelCase")]
905pub enum TransformSemanticObservationValueAxisV0 {
906    Value,
907    Important,
908}
909
910#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
911#[serde(rename_all = "camelCase")]
912pub enum TransformSemanticObservationOrderingRuleV0 {
913    SourceOrder,
914    ImportantPrecedence,
915}
916
917#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
918#[serde(rename_all = "camelCase")]
919pub enum TransformSemanticUnobservedAxisV0 {
920    InterSelectorSpecificityCompetition,
921    CascadeLayerOrder,
922    Origin,
923    ScopeProximity,
924    DomDependentMatching,
925    Inheritance,
926    CustomPropertyEnvironment,
927    AnimationAndTransition,
928}
929
930#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
931#[serde(rename_all = "camelCase")]
932pub enum TransformSemanticPreservationClaimScopeV0 {
933    ObservedSurfaceOnly,
934}
935
936#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
937#[serde(rename_all = "camelCase")]
938pub enum TransformSemanticPreservationVocabularyReviewV0 {
939    DeferredUntilFullCascadeObservation,
940}
941
942/// Declares exactly which semantic projection the transform guard compares.
943#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
944#[serde(rename_all = "camelCase")]
945pub struct TransformSemanticObservationSurfaceV0 {
946    pub key_axes: Vec<TransformSemanticObservationKeyAxisV0>,
947    pub value_axes: Vec<TransformSemanticObservationValueAxisV0>,
948    pub ordering_rules: Vec<TransformSemanticObservationOrderingRuleV0>,
949    pub unobserved_axes: Vec<TransformSemanticUnobservedAxisV0>,
950    pub claim_scope: TransformSemanticPreservationClaimScopeV0,
951    pub vocabulary_review: TransformSemanticPreservationVocabularyReviewV0,
952}
953
954#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
955#[serde(rename_all = "camelCase")]
956pub struct TransformSemanticPreservationTelemetryV0 {
957    pub observed_pass_count: u64,
958    pub preserved_pass_count: u64,
959    pub blocked_pass_count: u64,
960    pub observed_surface: TransformSemanticObservationSurfaceV0,
961}
962
963#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
964#[serde(rename_all = "camelCase")]
965pub struct TransformDischargeLedgerTelemetryV0 {
966    pub lookup_count: u64,
967    pub matched_lookup_count: u64,
968    pub accepted_stamp_count: u64,
969    pub blocked_lookup_count: u64,
970}
971
972#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
973#[serde(rename_all = "camelCase")]
974pub struct TransformExecutionSummaryV0 {
975    pub schema_version: &'static str,
976    pub product: &'static str,
977    pub input_byte_len: usize,
978    pub output_byte_len: usize,
979    pub requested_pass_ids: Vec<&'static str>,
980    pub ordered_pass_ids: Vec<&'static str>,
981    pub executed_pass_ids: Vec<&'static str>,
982    pub planned_only_pass_ids: Vec<&'static str>,
983    pub mutation_count: usize,
984    pub provenance_preserved: bool,
985    pub output_css: String,
986    pub css_module_evaluation: Option<TransformModuleEvaluationV0>,
987    pub css_import_inlines: Vec<TransformImportInlineV0>,
988    pub css_module_composes_exports: Vec<TransformCssModuleComposesResolutionV0>,
989    pub design_token_routes: Vec<TransformDesignTokenRouteV0>,
990    pub semantic_removals: Vec<TransformSemanticRemovalV0>,
991    #[serde(skip_serializing_if = "Option::is_none")]
992    pub module_qualified_shake: Option<TransformModuleQualifiedShakeSummaryV0>,
993    pub cascade_proof_obligations: TransformCascadeProofObligationReportV0,
994    #[serde(skip_serializing_if = "Vec::is_empty")]
995    pub winner_equality_obligations: Vec<TransformWinnerEqualityObligationV0>,
996    pub provenance_derivation_forest: TransformProvenanceDerivationForestV0,
997    pub structural_ir_transaction_telemetry: TransformStructuralIrTransactionTelemetryV0,
998    pub semantic_preservation_telemetry: TransformSemanticPreservationTelemetryV0,
999    pub discharge_ledger_telemetry: TransformDischargeLedgerTelemetryV0,
1000    pub strict_policy: TransformStrictPolicySummaryV0,
1001    pub decisions: Vec<TransformDecision>,
1002    pub outcomes: Vec<TransformPassExecutionOutcomeV0>,
1003    pub pass_plan: TransformPassPlanV0,
1004}
1005
1006#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1007#[serde(rename_all = "camelCase")]
1008pub struct TransformModuleQualifiedShakeSummaryV0 {
1009    pub module_instance: ModuleInstanceKeyV0,
1010    pub removed_count: usize,
1011}
1012
1013#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1014#[serde(
1015    tag = "kind",
1016    rename_all = "camelCase",
1017    rename_all_fields = "camelCase"
1018)]
1019pub enum TransformModuleQualifiedExecutionErrorV0 {
1020    UnknownModuleInstance {
1021        module_instance: ModuleInstanceKeyV0,
1022    },
1023}
1024
1025#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1026#[serde(rename_all = "camelCase")]
1027pub struct TransformCascadeProofObligationReportV0 {
1028    pub schema_version: &'static str,
1029    pub product: &'static str,
1030    pub obligation_count: usize,
1031    pub accepted_count: usize,
1032    pub blocked_count: usize,
1033    pub checked_pass_ids: Vec<&'static str>,
1034    pub obligations: Vec<TransformCascadeProofObligationV0>,
1035}
1036
1037#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1038#[serde(rename_all = "camelCase")]
1039pub struct TransformCascadeProofObligationV0 {
1040    pub pass_id: &'static str,
1041    pub proof_product: &'static str,
1042    pub accepted: bool,
1043    pub blocked_reason: Option<String>,
1044    pub provenance_preserved: bool,
1045    pub cascade_safe_witness: String,
1046    pub source_span_start: Option<usize>,
1047    pub source_span_end: Option<usize>,
1048    pub checked_obligations: Vec<&'static str>,
1049    #[serde(skip_serializing_if = "Option::is_none")]
1050    pub canonical_smt_input: Option<CanonicalSmtInputV0>,
1051    #[serde(skip_serializing_if = "Option::is_none")]
1052    pub discharge_ledger_lookup: Option<DischargeLedgerLookupV0>,
1053    #[serde(skip_serializing_if = "Option::is_none")]
1054    pub discharge_evidence: Option<TransformDischargeEvidenceV0>,
1055    pub proof_payload: Value,
1056}
1057
1058#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1059#[serde(rename_all = "camelCase")]
1060pub struct TransformSemanticRemovalV0 {
1061    pub pass_id: &'static str,
1062    pub symbol_kind: &'static str,
1063    pub name: String,
1064    pub source_span_start: usize,
1065    pub source_span_end: usize,
1066    pub reason: &'static str,
1067    pub certainty: &'static str,
1068    pub derivation_steps: Vec<&'static str>,
1069}
1070
1071#[derive(Debug, Clone, PartialEq, Eq)]
1072pub(crate) struct TransformSemanticRemovalCandidate {
1073    pub(crate) symbol_kind: &'static str,
1074    pub(crate) name: String,
1075    pub(crate) source_span_start: usize,
1076    pub(crate) source_span_end: usize,
1077    pub(crate) reason: &'static str,
1078}
1079
1080impl TransformSemanticRemovalCandidate {
1081    pub(crate) fn into_public(self, pass_id: &'static str) -> TransformSemanticRemovalV0 {
1082        TransformSemanticRemovalV0 {
1083            pass_id,
1084            symbol_kind: self.symbol_kind,
1085            name: self.name,
1086            source_span_start: self.source_span_start,
1087            source_span_end: self.source_span_end,
1088            reason: self.reason,
1089            certainty: "high",
1090            derivation_steps: vec![
1091                "closedStyleWorld",
1092                "reachableRootSetComputed",
1093                "symbolNotMarkedReachable",
1094                "sourceRangeRemoved",
1095            ],
1096        }
1097    }
1098}
1099
1100#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1101#[serde(rename_all = "camelCase")]
1102pub struct TransformIncrementalExecutionSummaryV0 {
1103    pub schema_version: &'static str,
1104    pub product: &'static str,
1105    pub incremental_engine: &'static str,
1106    pub query_model: &'static str,
1107    pub reuse_policy: &'static str,
1108    pub reused_previous_execution: bool,
1109    pub incremental_plan: IncrementalComputationPlanV0,
1110    pub next_snapshot: IncrementalSnapshotV0,
1111    pub execution: TransformExecutionSummaryV0,
1112    pub ready_surfaces: Vec<&'static str>,
1113}
1114
1115#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1116#[serde(rename_all = "camelCase")]
1117pub struct TransformCascadeSafetyFuzzCaseV0 {
1118    pub seed: u64,
1119    pub pass_count: usize,
1120}
1121
1122#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1123#[serde(rename_all = "camelCase")]
1124pub struct TransformCascadeSafetyFuzzResultV0 {
1125    pub seed: u64,
1126    pub pass_count: usize,
1127    pub requested_pass_ids: Vec<&'static str>,
1128    pub executed_pass_ids: Vec<&'static str>,
1129    pub output_byte_len: usize,
1130    pub output_token_count: usize,
1131    pub output_error_count: usize,
1132    pub provenance_node_count: usize,
1133    pub passed: bool,
1134}
1135
1136#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
1137#[serde(rename_all = "camelCase")]
1138pub struct TransformFuzzSeedReportV0 {
1139    pub schema_version: &'static str,
1140    pub product: &'static str,
1141    pub case_count: usize,
1142    pub passed_count: usize,
1143    pub failed_count: usize,
1144    pub results: Vec<TransformCascadeSafetyFuzzResultV0>,
1145}
1146
1147#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
1148#[serde(default, rename_all = "camelCase")]
1149pub struct TransformExecutionContextV0 {
1150    pub drop_dark_mode_media_queries: bool,
1151    pub supports_target_capability: Option<SupportsTargetCapabilityV0>,
1152    pub vendor_prefix_policy: Option<TransformVendorPrefixPolicyV0>,
1153    pub reachable_class_names: Vec<String>,
1154    pub reachable_keyframe_names: Vec<String>,
1155    pub reachable_value_names: Vec<String>,
1156    pub reachable_custom_property_names: Vec<String>,
1157    pub scss_module_evaluation: Option<TransformModuleEvaluationV0>,
1158    pub less_module_evaluation: Option<TransformModuleEvaluationV0>,
1159    pub import_inlines: Vec<TransformImportInlineV0>,
1160    pub class_name_rewrites: Vec<TransformClassNameRewriteV0>,
1161    pub css_module_composes_resolutions: Vec<TransformCssModuleComposesResolutionV0>,
1162    pub css_module_value_resolutions: Vec<TransformCssModuleValueResolutionV0>,
1163    pub design_token_routes: Vec<TransformDesignTokenRouteV0>,
1164    /// Complete declarations outside the transformed stylesheet that may
1165    /// participate in the cascade. Absence keeps winner trust fail-closed.
1166    #[serde(default, skip_serializing_if = "Option::is_none")]
1167    pub cascade_environment: Option<TransformCascadeEnvironmentV0>,
1168}
1169
1170#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
1171#[serde(default, rename_all = "camelCase")]
1172pub struct TransformCascadeEnvironmentV0 {
1173    /// Global source-order coordinate assigned to the first declaration in
1174    /// the transformed stylesheet.
1175    pub stylesheet_source_order_base: u32,
1176    pub declarations: Vec<TransformCascadeEnvironmentDeclarationV0>,
1177}
1178
1179#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1180#[serde(rename_all = "camelCase")]
1181pub struct TransformCascadeEnvironmentDeclarationV0 {
1182    pub declaration_id: String,
1183    pub selector: String,
1184    pub property: String,
1185    pub value: String,
1186    pub origin: CascadeOriginV0,
1187    pub important: bool,
1188    #[serde(default, skip_serializing_if = "Option::is_none")]
1189    pub layer_rank: Option<i32>,
1190    #[serde(default, skip_serializing_if = "Option::is_none")]
1191    pub scope_proximity: Option<u32>,
1192    pub source_order: u32,
1193}
1194
1195#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
1196#[serde(rename_all = "camelCase")]
1197pub struct TransformVendorPrefixPolicyV0 {
1198    pub webkit: bool,
1199    pub moz: bool,
1200    pub ms: bool,
1201}
1202
1203impl TransformVendorPrefixPolicyV0 {
1204    pub const fn none() -> Self {
1205        Self {
1206            webkit: false,
1207            moz: false,
1208            ms: false,
1209        }
1210    }
1211
1212    pub const fn conservative() -> Self {
1213        Self {
1214            webkit: true,
1215            moz: true,
1216            ms: true,
1217        }
1218    }
1219
1220    pub const fn is_empty(self) -> bool {
1221        !(self.webkit || self.moz || self.ms)
1222    }
1223
1224    pub fn allows_prefix(self, prefixed_name: &str) -> bool {
1225        if prefixed_name.starts_with("-webkit-") {
1226            return self.webkit;
1227        }
1228        if prefixed_name.starts_with("-moz-") {
1229            return self.moz;
1230        }
1231        if prefixed_name.starts_with("-ms-") {
1232            return self.ms;
1233        }
1234        true
1235    }
1236}
1237
1238#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1239#[serde(rename_all = "camelCase")]
1240pub struct TransformModuleEvaluationV0 {
1241    pub evaluator: String,
1242    #[serde(default, skip_serializing_if = "Option::is_none")]
1243    pub product_output_source: Option<String>,
1244    pub evaluated_css: String,
1245    #[serde(default, skip_serializing_if = "Option::is_none")]
1246    pub native_edit_output: Option<String>,
1247    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1248    pub native_replacements: Vec<TransformModuleEvaluationNativeReplacementV0>,
1249    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1250    pub native_edits: Vec<TransformModuleEvaluationNativeEditV0>,
1251    #[serde(default, skip_serializing_if = "Option::is_none")]
1252    pub oracle: Option<TransformModuleEvaluationOracleV0>,
1253}
1254
1255impl TransformModuleEvaluationV0 {
1256    pub fn declares_native_product_output(&self) -> bool {
1257        self.product_output_source
1258            .as_deref()
1259            .is_some_and(|source| source == "nativeEditOutput")
1260    }
1261
1262    // HONESTY NOTE: `divergence_count == 0` is a value-WELL-FORMEDNESS self-check on the
1263    // native-edit output (every native-emitted declaration value canonically round-trips), NOT a
1264    // differential against an external SCSS/Less compiler. So this gate means "native output is
1265    // self-consistent and value-preserving", NOT "native agrees with dart-sass/lessc". External
1266    // agreement is witnessed separately by the `externalDifferential` gate
1267    // (`scripts/check-rust-omena-diff-test-external-corpus-differential.ts`, pinned dart-sass/lessc) over
1268    // its covered fixture slices only; this self-check stays the cheap inner oracle for every
1269    // evaluated candidate, and the production rail remains a self-comparison.
1270    pub fn oracle_allows_native_product_output(&self) -> bool {
1271        self.oracle.as_ref().is_some_and(|oracle| {
1272            oracle.mode == "oracleOnly"
1273                && oracle.divergence_count == 0
1274                && oracle.all_legacy_declaration_values_preserved
1275        })
1276    }
1277
1278    pub fn may_consume_native_product_output(&self) -> bool {
1279        self.declares_native_product_output() && self.oracle_allows_native_product_output()
1280    }
1281
1282    // NOTE: the "retained oracle" here is the retained product-output string (`evaluated_css`),
1283    // which in the production rail is itself native-derived — so this is a byte-equality
1284    // self-consistency check between two native-derived strings, not a comparison to an
1285    // independent external evaluator.
1286    pub fn native_output_matches_retained_oracle(&self, native_output: &str) -> bool {
1287        self.oracle
1288            .as_ref()
1289            .is_some_and(|_| native_output == self.evaluated_css)
1290    }
1291}
1292
1293#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1294#[serde(rename_all = "camelCase")]
1295pub struct TransformModuleEvaluationNativeReplacementV0 {
1296    pub name: String,
1297    pub start: usize,
1298    pub end: usize,
1299    pub text: String,
1300    #[serde(default, skip_serializing_if = "Option::is_none")]
1301    pub rendered_value: Option<String>,
1302    pub abstract_value: AbstractCssValueV0,
1303    pub abstract_value_kind: String,
1304}
1305
1306#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1307#[serde(rename_all = "camelCase")]
1308pub struct TransformModuleEvaluationNativeEditV0 {
1309    pub start: usize,
1310    pub end: usize,
1311    pub replacement: String,
1312    pub edit_kind: String,
1313    #[serde(default, skip_serializing_if = "Option::is_none")]
1314    pub abstract_value: Option<AbstractCssValueV0>,
1315    #[serde(default, skip_serializing_if = "Option::is_none")]
1316    pub abstract_value_kind: Option<String>,
1317}
1318
1319#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
1320#[serde(default, rename_all = "camelCase")]
1321pub struct TransformModuleEvaluationOracleV0 {
1322    pub mode: String,
1323    pub product_output_source: String,
1324    pub legacy_declaration_value_count: usize,
1325    pub abstract_value_count: usize,
1326    pub exact_value_count: usize,
1327    pub raw_value_count: usize,
1328    pub bottom_value_count: usize,
1329    pub top_value_count: usize,
1330    pub divergence_count: usize,
1331    pub all_legacy_declaration_values_preserved: bool,
1332    pub native_replacement_count: usize,
1333    pub native_replacement_legacy_reflection_count: usize,
1334    pub native_replacement_legacy_unreflected_count: usize,
1335    pub native_value_reference_count: usize,
1336    pub native_resolved_value_count: usize,
1337    pub native_raw_value_count: usize,
1338    pub native_top_value_count: usize,
1339    pub native_cycle_count: usize,
1340    pub native_fuel_exhausted_count: usize,
1341    pub native_unresolved_reference_count: usize,
1342    pub native_unsupported_dynamic_count: usize,
1343}
1344
1345#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1346#[serde(rename_all = "camelCase")]
1347pub struct TransformImportInlineV0 {
1348    pub import_source: String,
1349    pub replacement_css: String,
1350}
1351
1352#[derive(Debug, Clone, PartialEq, Eq)]
1353pub struct TransformLessInlineLiteralPlaceholderV0 {
1354    pub placeholder: String,
1355    pub literal_css: String,
1356}
1357
1358#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1359#[serde(rename_all = "camelCase")]
1360pub struct TransformClassNameRewriteV0 {
1361    pub original_name: String,
1362    pub rewritten_name: String,
1363}
1364
1365#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1366#[serde(rename_all = "camelCase")]
1367pub struct TransformCssModuleComposesResolutionV0 {
1368    pub local_class_name: String,
1369    pub exported_class_names: Vec<String>,
1370}
1371
1372#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1373#[serde(rename_all = "camelCase")]
1374pub struct TransformCssModuleValueResolutionV0 {
1375    pub local_name: String,
1376    pub resolved_value: String,
1377}
1378
1379#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
1380#[serde(rename_all = "camelCase")]
1381pub struct TransformDesignTokenRouteV0 {
1382    pub token_name: String,
1383    pub routed_value: String,
1384}
1385
1386#[cfg(test)]
1387mod evidence_graph_tests {
1388    use super::*;
1389    use omena_cascade::{
1390        CascadeKey, CascadeValue, LayerRank, ModuleRank, Specificity, cascade_property,
1391    };
1392
1393    fn winner_equality_test_declaration(
1394        id: &str,
1395        value: &str,
1396        source_order: u32,
1397    ) -> CascadeDeclaration {
1398        CascadeDeclaration {
1399            id: id.to_string(),
1400            property: "color".to_string(),
1401            value: CascadeValue::Literal(value.to_string()),
1402            key: CascadeKey::new(
1403                CascadeLevel::AuthorNormal,
1404                LayerRank(0),
1405                0,
1406                Specificity::new(0, 1, 0),
1407                ModuleRank::ZERO,
1408                source_order,
1409            ),
1410            specificity_exactness: omena_cascade::SpecificityExactnessV0::Exact,
1411        }
1412    }
1413
1414    #[test]
1415    fn winner_equality_witness_consumes_the_cascade_authority_outcome() -> Result<(), String> {
1416        let outcome = cascade_property(
1417            [
1418                winner_equality_test_declaration("earlier", "red", 0),
1419                winner_equality_test_declaration("later", "blue", 1),
1420            ],
1421            "color",
1422        );
1423        let witness = TransformWinnerEqualityWitnessV0::from_cascade_outcome(&outcome)
1424            .ok_or_else(|| "the closed cascade should have a definite winner".to_string())?;
1425
1426        assert_eq!(witness.winner.id, "later");
1427        assert_eq!(
1428            witness.proof,
1429            CascadeProof::from_declaration(&witness.winner)
1430        );
1431        Ok(())
1432    }
1433
1434    #[test]
1435    fn winner_equality_witness_stays_absent_for_non_definite_outcomes() {
1436        assert!(
1437            TransformWinnerEqualityWitnessV0::from_cascade_outcome(&CascadeOutcome::Top).is_none()
1438        );
1439        assert!(
1440            TransformWinnerEqualityWitnessV0::from_cascade_outcome(&CascadeOutcome::Inherit)
1441                .is_none()
1442        );
1443    }
1444
1445    #[test]
1446    fn winner_equality_trust_records_name_covered_axes() -> Result<(), serde_json::Error> {
1447        let tier = TransformSemanticGuaranteeTierV0::WinnerEqualityObserved {
1448            axes: vec![
1449                TransformWinnerEqualityAxisV0::CascadeLevel,
1450                TransformWinnerEqualityAxisV0::LayerRank,
1451            ],
1452        };
1453
1454        assert_eq!(
1455            serde_json::to_value(tier)?,
1456            serde_json::json!({
1457                "kind": "winnerEqualityObserved",
1458                "axes": ["cascadeLevel", "layerRank"]
1459            })
1460        );
1461        Ok(())
1462    }
1463
1464    #[test]
1465    fn winner_equality_absence_names_the_undriven_level() -> Result<(), serde_json::Error> {
1466        let tier = TransformSemanticGuaranteeTierV0::Absent {
1467            reasons: vec![TransformWinnerEqualityAbsenceV0 {
1468                axis: TransformWinnerEqualityAxisV0::CascadeLevel,
1469                reason: TransformWinnerEqualityAbsenceReasonV0::DriverUnavailable {
1470                    level: Some(CascadeLevel::Animation),
1471                },
1472            }],
1473        };
1474
1475        assert_eq!(
1476            serde_json::to_value(tier)?,
1477            serde_json::json!({
1478                "kind": "absent",
1479                "reasons": [{
1480                    "axis": "cascadeLevel",
1481                    "reason": {
1482                        "kind": "driverUnavailable",
1483                        "level": "animation"
1484                    }
1485                }]
1486            })
1487        );
1488        Ok(())
1489    }
1490
1491    #[test]
1492    fn transform_outcome_evidence_graph_preserves_public_shape() -> Result<(), serde_json::Error> {
1493        let outcome = TransformPassExecutionOutcomeV0 {
1494            pass_id: "number-compression",
1495            status: TransformPassRuntimeStatus::Applied,
1496            input_byte_len: 32,
1497            output_byte_len: 28,
1498            mutation_count: 1,
1499            provenance_preserved: true,
1500            detail: "fixture pass",
1501        };
1502
1503        let before = serde_json::to_value(&outcome)?;
1504        let node = outcome.evidence_node_seed();
1505        let graph = build_evidence_graph_from_edges_v0([node], [outcome.evidence_demand_edge()])
1506            .map_err(|_| serde::ser::Error::custom("outcome edge must target its node"))?;
1507        let after = serde_json::to_value(&outcome)?;
1508
1509        assert_eq!(before, after);
1510        assert_eq!(graph.nodes.len(), 1);
1511        assert_eq!(graph.nodes[0].key.input_identity, "number-compression");
1512        assert_eq!(graph.nodes[0].guarantee, GuaranteeKindV0::Floor);
1513        assert!(
1514            graph.nodes[0]
1515                .provenance
1516                .iter()
1517                .any(|item| item == "mutationCount:1")
1518        );
1519        Ok(())
1520    }
1521
1522    #[test]
1523    fn transform_derivation_forest_evidence_graph_preserves_public_shape()
1524    -> Result<(), serde_json::Error> {
1525        let forest = TransformProvenanceDerivationForestV0 {
1526            schema_version: "0",
1527            product: "omena-transform-passes.provenance-derivation-forest",
1528            root_count: 1,
1529            node_count: 1,
1530            nodes: vec![TransformProvenanceDerivationNodeV0 {
1531                node_index: 0,
1532                parent_index: None,
1533                pass_id: "comment-strip",
1534                status: TransformPassRuntimeStatus::Applied,
1535                input_byte_len: 48,
1536                output_byte_len: 36,
1537                source_span_start: 0,
1538                source_span_end: 12,
1539                generated_span_start: 0,
1540                generated_span_end: 0,
1541                mutation_spans: Vec::new(),
1542                mutation_count: 1,
1543                provenance_preserved: true,
1544                detail: "fixture derivation",
1545            }],
1546        };
1547
1548        let before = serde_json::to_value(&forest)?;
1549        let graph = forest
1550            .evidence_graph()
1551            .map_err(|_| serde::ser::Error::custom("forest edge must target its node"))?;
1552        let after = serde_json::to_value(&forest)?;
1553
1554        assert_eq!(before, after);
1555        assert_eq!(graph.nodes.len(), 1);
1556        assert_eq!(graph.nodes[0].key.input_identity, "comment-strip#0");
1557        assert_eq!(graph.nodes[0].guarantee, GuaranteeKindV0::Floor);
1558        Ok(())
1559    }
1560}