1use omena_evidence_graph::{EvidenceNodeKeyV0, GuaranteeKindV0};
2use omena_parser::{
3 ClosedWorldBundleV0, ModuleInstanceKeyV0, ModuleQualifiedSymbolSetV0, ParserByteSpanV0,
4 ParserPositionV0, ParserRangeV0,
5};
6use omena_query_core::{FactPrecision, fact_precision_from_analysis_precision};
7use omena_query_transform_runner::{
8 TransformDecision, TransformExecutionContextV0, TransformSemanticGuaranteeTierV0,
9 TransformStrictPolicyEventV0, TransformStrictPolicySummaryV0,
10};
11use serde::Serialize;
12
13use crate::{
14 OmenaQueryCascadeAtPositionV0, OmenaQueryClassSiteValueV0,
15 OmenaQuerySourcePrecisionReferenceV0, OmenaQueryStyleDiagnosticV0,
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
19#[serde(rename_all = "camelCase")]
20pub enum OmenaQueryExplainAvailabilityV0 {
21 Available,
22 NotYetAvailable,
23 NotFound,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
27#[serde(rename_all = "camelCase")]
28pub enum OmenaQueryExplainCapabilityV0 {
29 Diagnostic,
30 Transform,
31 TreeShake,
32 Precision,
33 Cascade,
34 Bundle,
35 HoverTrace,
36 ClassSite,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
40#[serde(rename_all = "camelCase")]
41pub enum OmenaQueryExplainSymbolKindV0 {
42 Class,
43 Keyframes,
44 Value,
45 CustomProperty,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
49#[serde(
50 tag = "kind",
51 rename_all = "camelCase",
52 rename_all_fields = "camelCase"
53)]
54pub enum OmenaQueryExplainTargetV0 {
55 Diagnostic {
56 style_path: String,
57 code: String,
58 range: ParserRangeV0,
59 },
60 Transform {
61 pass_id: String,
62 decision_ordinal: usize,
63 },
64 TreeShake {
65 symbol_kind: OmenaQueryExplainSymbolKindV0,
66 symbol_name: String,
67 },
68 Precision {
69 source_path: String,
70 variable_name: String,
71 reference_byte_offset: usize,
72 },
73 Cascade {
74 style_path: String,
75 position: ParserPositionV0,
76 },
77 Bundle {
78 chunk_reference: String,
79 },
80 HoverTrace {
81 document_uri: String,
82 position: Option<ParserPositionV0>,
83 },
84 ClassSite {
85 source_path: String,
86 site_byte_span: ParserByteSpanV0,
87 },
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
91#[serde(
92 tag = "kind",
93 rename_all = "camelCase",
94 rename_all_fields = "camelCase"
95)]
96pub enum OmenaQueryExplainFactReferenceV0 {
97 Diagnostic {
98 style_path: String,
99 code: String,
100 range: ParserRangeV0,
101 evidence_node_key: EvidenceNodeKeyV0,
102 },
103 TransformOutcome {
104 pass_id: String,
105 decision_ordinal: usize,
106 evidence_node_key: EvidenceNodeKeyV0,
107 },
108 ClosedWorldReachability {
109 closure_hash: String,
110 symbol_kind: OmenaQueryExplainSymbolKindV0,
111 symbol_name: String,
112 guarantee: GuaranteeKindV0,
113 },
114 PrecisionFact {
115 source_path: String,
116 variable_name: String,
117 reference_byte_offset: usize,
118 },
119 CascadeResolution {
120 style_path: String,
121 position: ParserPositionV0,
122 winner_range: Option<ParserRangeV0>,
123 },
124 CapabilityGate {
125 capability: OmenaQueryExplainCapabilityV0,
126 },
127 HoverResolution {
128 document_uri: String,
129 position: Option<ParserPositionV0>,
130 reason_code: String,
131 },
132 ClassSiteFact {
133 source_path: String,
134 site_byte_span: ParserByteSpanV0,
135 },
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
139#[serde(
140 tag = "kind",
141 rename_all = "camelCase",
142 rename_all_fields = "camelCase"
143)]
144pub enum OmenaQueryExplainFactValueV0 {
145 DiagnosticIdentity {
146 severity: String,
147 },
148 ProvenanceLabel {
149 label: String,
150 },
151 TransformDecision {
152 decision_kind: &'static str,
153 status: String,
154 mutation_count: usize,
155 provenance_preserved: bool,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 semantic_guarantee_tier: Option<TransformSemanticGuaranteeTierV0>,
158 refused_count: usize,
159 rolled_back_count: usize,
160 refusal_reasons: Vec<TransformStrictPolicyEventV0>,
161 rollback_reasons: Vec<TransformStrictPolicyEventV0>,
162 },
163 ReachabilityMembership {
164 reachable: bool,
165 },
166 PrecisionClassification {
167 precision: FactPrecision,
168 resolved_tier: String,
169 },
170 CascadeResolution {
171 status: String,
172 candidate_count: usize,
173 winner_source_order: Option<usize>,
174 },
175 CapabilityAvailability {
176 availability: OmenaQueryExplainAvailabilityV0,
177 },
178 HoverResolution {
179 matched: bool,
180 candidate_count: usize,
181 definition_count: usize,
182 },
183 ClassSiteValue {
184 value: Box<OmenaQueryClassSiteValueV0>,
185 },
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
199#[serde(rename_all = "camelCase")]
200pub struct OmenaQueryExplainFactV0 {
201 reference: OmenaQueryExplainFactReferenceV0,
202 value: OmenaQueryExplainFactValueV0,
203}
204
205impl OmenaQueryExplainFactV0 {
206 fn new(
207 reference: OmenaQueryExplainFactReferenceV0,
208 value: OmenaQueryExplainFactValueV0,
209 ) -> Self {
210 Self { reference, value }
211 }
212
213 pub fn reference(&self) -> &OmenaQueryExplainFactReferenceV0 {
214 &self.reference
215 }
216
217 pub fn value(&self) -> &OmenaQueryExplainFactValueV0 {
218 &self.value
219 }
220}
221
222#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
223#[serde(rename_all = "camelCase")]
224pub struct OmenaQueryExplainSourceSpanV0 {
225 source_path: String,
226 range: ParserRangeV0,
227 fact_reference: OmenaQueryExplainFactReferenceV0,
228}
229
230impl OmenaQueryExplainSourceSpanV0 {
231 fn new(
232 source_path: impl Into<String>,
233 range: ParserRangeV0,
234 fact_reference: OmenaQueryExplainFactReferenceV0,
235 ) -> Self {
236 Self {
237 source_path: source_path.into(),
238 range,
239 fact_reference,
240 }
241 }
242}
243
244#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
245#[serde(rename_all = "camelCase")]
246pub struct OmenaQueryExplainResponseV0 {
247 schema_version: &'static str,
248 product: &'static str,
249 target: OmenaQueryExplainTargetV0,
250 availability: OmenaQueryExplainAvailabilityV0,
251 primary_fact: OmenaQueryExplainFactV0,
252 supporting_facts: Vec<OmenaQueryExplainFactV0>,
253 related_spans: Vec<OmenaQueryExplainSourceSpanV0>,
254}
255
256impl OmenaQueryExplainResponseV0 {
257 fn new(
258 target: OmenaQueryExplainTargetV0,
259 availability: OmenaQueryExplainAvailabilityV0,
260 primary_fact: OmenaQueryExplainFactV0,
261 supporting_facts: Vec<OmenaQueryExplainFactV0>,
262 related_spans: Vec<OmenaQueryExplainSourceSpanV0>,
263 ) -> Self {
264 Self {
265 schema_version: "0",
266 product: "omena-query.explain",
267 target,
268 availability,
269 primary_fact,
270 supporting_facts,
271 related_spans,
272 }
273 }
274
275 pub fn target(&self) -> &OmenaQueryExplainTargetV0 {
276 &self.target
277 }
278
279 pub const fn availability(&self) -> OmenaQueryExplainAvailabilityV0 {
280 self.availability
281 }
282
283 pub fn primary_fact(&self) -> &OmenaQueryExplainFactV0 {
284 &self.primary_fact
285 }
286
287 pub fn supporting_facts(&self) -> &[OmenaQueryExplainFactV0] {
288 self.supporting_facts.as_slice()
289 }
290
291 pub fn related_spans(&self) -> &[OmenaQueryExplainSourceSpanV0] {
292 self.related_spans.as_slice()
293 }
294}
295
296#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
297#[serde(rename_all = "camelCase")]
298#[non_exhaustive]
299pub struct OmenaQueryModuleTreeShakeExplanationV0 {
300 schema_version: &'static str,
301 product: &'static str,
302 module_instance: ModuleInstanceKeyV0,
303 symbol_kind: OmenaQueryExplainSymbolKindV0,
304 symbol_name: String,
305 availability: OmenaQueryExplainAvailabilityV0,
306 reachable: Option<bool>,
307 flat_reachable: bool,
308 emission_guard_retained: bool,
309 closure_hash: String,
310 ownership_digest: String,
311 guarantee: GuaranteeKindV0,
312 provenance_labels: Vec<&'static str>,
313}
314
315impl OmenaQueryModuleTreeShakeExplanationV0 {
316 pub fn module_instance(&self) -> &ModuleInstanceKeyV0 {
317 &self.module_instance
318 }
319
320 pub const fn symbol_kind(&self) -> OmenaQueryExplainSymbolKindV0 {
321 self.symbol_kind
322 }
323
324 pub fn symbol_name(&self) -> &str {
325 &self.symbol_name
326 }
327
328 pub const fn availability(&self) -> OmenaQueryExplainAvailabilityV0 {
329 self.availability
330 }
331
332 pub const fn reachable(&self) -> Option<bool> {
337 self.reachable
338 }
339
340 pub const fn flat_reachable(&self) -> bool {
341 self.flat_reachable
342 }
343
344 pub const fn emission_guard_retained(&self) -> bool {
349 self.emission_guard_retained
350 }
351
352 pub fn ownership_digest(&self) -> &str {
353 &self.ownership_digest
354 }
355
356 pub fn closure_hash(&self) -> &str {
357 &self.closure_hash
358 }
359
360 pub const fn guarantee(&self) -> GuaranteeKindV0 {
361 self.guarantee
362 }
363
364 pub fn provenance_labels(&self) -> &[&'static str] {
365 self.provenance_labels.as_slice()
366 }
367}
368
369pub enum OmenaQueryExplainInputV0<'a> {
370 Diagnostic {
371 style_path: &'a str,
372 diagnostic: &'a OmenaQueryStyleDiagnosticV0,
373 },
374 Transform {
375 decision: &'a TransformDecision,
376 decision_ordinal: usize,
377 },
378 TransformWithPolicy {
379 decision: &'a TransformDecision,
380 decision_ordinal: usize,
381 strict_policy: &'a TransformStrictPolicySummaryV0,
382 },
383 TreeShake {
384 bundle: &'a ClosedWorldBundleV0,
385 symbol_kind: OmenaQueryExplainSymbolKindV0,
386 symbol_name: &'a str,
387 },
388 Precision {
389 reference: &'a OmenaQuerySourcePrecisionReferenceV0,
390 },
391 Cascade {
392 result: &'a OmenaQueryCascadeAtPositionV0,
393 },
394 BundleUnavailable {
395 chunk_reference: &'a str,
396 },
397 HoverTrace {
398 document_uri: &'a str,
399 position: Option<ParserPositionV0>,
400 reason_code: &'a str,
401 matched: bool,
402 candidate_count: usize,
403 definition_count: usize,
404 },
405 ClassSite {
406 value: &'a OmenaQueryClassSiteValueV0,
407 },
408}
409
410pub fn explain_omena_query(input: OmenaQueryExplainInputV0<'_>) -> OmenaQueryExplainResponseV0 {
411 match input {
412 OmenaQueryExplainInputV0::Diagnostic {
413 style_path,
414 diagnostic,
415 } => explain_diagnostic(style_path, diagnostic),
416 OmenaQueryExplainInputV0::Transform {
417 decision,
418 decision_ordinal,
419 } => explain_transform(
420 decision,
421 decision_ordinal,
422 &TransformStrictPolicySummaryV0::default(),
423 ),
424 OmenaQueryExplainInputV0::TransformWithPolicy {
425 decision,
426 decision_ordinal,
427 strict_policy,
428 } => explain_transform(decision, decision_ordinal, strict_policy),
429 OmenaQueryExplainInputV0::TreeShake {
430 bundle,
431 symbol_kind,
432 symbol_name,
433 } => explain_tree_shake(bundle, symbol_kind, symbol_name),
434 OmenaQueryExplainInputV0::Precision { reference } => explain_precision(reference),
435 OmenaQueryExplainInputV0::ClassSite { value } => explain_class_site(value),
436 OmenaQueryExplainInputV0::Cascade { result } => explain_cascade(result),
437 OmenaQueryExplainInputV0::BundleUnavailable { chunk_reference } => {
438 explain_bundle_unavailable(chunk_reference)
439 }
440 OmenaQueryExplainInputV0::HoverTrace {
441 document_uri,
442 position,
443 reason_code,
444 matched,
445 candidate_count,
446 definition_count,
447 } => explain_hover_trace(
448 document_uri,
449 position,
450 reason_code,
451 matched,
452 candidate_count,
453 definition_count,
454 ),
455 }
456}
457
458pub fn explain_omena_query_tree_shake_for_style_source(
459 style_path: &str,
460 style_source: &str,
461 context: &TransformExecutionContextV0,
462 symbol_kind: OmenaQueryExplainSymbolKindV0,
463 symbol_name: &str,
464) -> Option<OmenaQueryExplainResponseV0> {
465 let requested_pass_id = match symbol_kind {
466 OmenaQueryExplainSymbolKindV0::Class => "tree-shake-class",
467 OmenaQueryExplainSymbolKindV0::Keyframes => "tree-shake-keyframes",
468 OmenaQueryExplainSymbolKindV0::Value => "tree-shake-value",
469 OmenaQueryExplainSymbolKindV0::CustomProperty => "tree-shake-custom-property",
470 };
471 let bundle = crate::style::build_closed_world_bundle_for_single_style_source_context(
472 style_path,
473 style_source,
474 &[requested_pass_id.to_string()],
475 context,
476 )?;
477 Some(explain_omena_query(OmenaQueryExplainInputV0::TreeShake {
478 bundle: &bundle,
479 symbol_kind,
480 symbol_name,
481 }))
482}
483
484pub fn explain_omena_query_tree_shake_for_module(
485 bundle: &ClosedWorldBundleV0,
486 module_instance: &ModuleInstanceKeyV0,
487 symbol_kind: OmenaQueryExplainSymbolKindV0,
488 symbol_name: &str,
489) -> OmenaQueryModuleTreeShakeExplanationV0 {
490 let module_symbols = bundle.reachability().symbols_for_module(module_instance);
491 let reachable = module_symbols.map(|symbols| {
492 symbols.is_reachable() && module_symbol_is_reachable(symbols, symbol_kind, symbol_name)
493 });
494 let flat_reachable = flat_symbol_is_reachable(bundle, symbol_kind, symbol_name);
495 let emission_guard_retained = false;
496 let mut provenance_labels = vec!["moduleQualifiedOwnershipObserved"];
497 if emission_guard_retained {
498 provenance_labels.push("emissionTokenCollisionGuardApplied");
499 }
500
501 OmenaQueryModuleTreeShakeExplanationV0 {
502 schema_version: "0",
503 product: "omena-query.module-tree-shake-explain",
504 module_instance: module_instance.clone(),
505 symbol_kind,
506 symbol_name: symbol_name.to_string(),
507 availability: if module_symbols.is_some() {
508 OmenaQueryExplainAvailabilityV0::Available
509 } else {
510 OmenaQueryExplainAvailabilityV0::NotFound
511 },
512 reachable,
513 flat_reachable,
514 emission_guard_retained,
515 closure_hash: bundle.closure_hash().to_string(),
516 ownership_digest: bundle.module_qualified_ownership_digest(),
517 guarantee: GuaranteeKindV0::NotClaimedExactTraversal,
518 provenance_labels,
519 }
520}
521
522fn explain_diagnostic(
523 style_path: &str,
524 diagnostic: &OmenaQueryStyleDiagnosticV0,
525) -> OmenaQueryExplainResponseV0 {
526 let evidence_node_key = EvidenceNodeKeyV0::new("diagnosticProvenance", diagnostic.code);
527 let reference = OmenaQueryExplainFactReferenceV0::Diagnostic {
528 style_path: style_path.to_string(),
529 code: diagnostic.code.to_string(),
530 range: diagnostic.range,
531 evidence_node_key,
532 };
533 let supporting_facts = diagnostic
534 .provenance
535 .iter()
536 .map(|label| {
537 OmenaQueryExplainFactV0::new(
538 reference.clone(),
539 OmenaQueryExplainFactValueV0::ProvenanceLabel {
540 label: (*label).to_string(),
541 },
542 )
543 })
544 .collect();
545
546 OmenaQueryExplainResponseV0::new(
547 OmenaQueryExplainTargetV0::Diagnostic {
548 style_path: style_path.to_string(),
549 code: diagnostic.code.to_string(),
550 range: diagnostic.range,
551 },
552 OmenaQueryExplainAvailabilityV0::Available,
553 OmenaQueryExplainFactV0::new(
554 reference.clone(),
555 OmenaQueryExplainFactValueV0::DiagnosticIdentity {
556 severity: diagnostic.severity.to_string(),
557 },
558 ),
559 supporting_facts,
560 vec![OmenaQueryExplainSourceSpanV0::new(
561 style_path,
562 diagnostic.range,
563 reference,
564 )],
565 )
566}
567
568fn explain_transform(
569 decision: &TransformDecision,
570 decision_ordinal: usize,
571 strict_policy: &TransformStrictPolicySummaryV0,
572) -> OmenaQueryExplainResponseV0 {
573 let outcome = decision.compatibility_outcome();
574 let decision_kind = match decision {
575 TransformDecision::Applied { .. } => "applied",
576 TransformDecision::NoChange { .. } => "noChange",
577 TransformDecision::Blocked { .. } => "blocked",
578 TransformDecision::Rejected { .. } => "rejected",
579 };
580 let reference = OmenaQueryExplainFactReferenceV0::TransformOutcome {
581 pass_id: outcome.pass_id.to_string(),
582 decision_ordinal,
583 evidence_node_key: outcome.evidence_node_key(),
584 };
585 OmenaQueryExplainResponseV0::new(
586 OmenaQueryExplainTargetV0::Transform {
587 pass_id: outcome.pass_id.to_string(),
588 decision_ordinal,
589 },
590 OmenaQueryExplainAvailabilityV0::Available,
591 OmenaQueryExplainFactV0::new(
592 reference,
593 OmenaQueryExplainFactValueV0::TransformDecision {
594 decision_kind,
595 status: format!("{:?}", outcome.status),
596 mutation_count: outcome.mutation_count,
597 provenance_preserved: outcome.provenance_preserved,
598 semantic_guarantee_tier: decision.semantic_guarantee_tier().cloned(),
599 refused_count: strict_policy.refused_count,
600 rolled_back_count: strict_policy.rolled_back_count,
601 refusal_reasons: strict_policy.refusal_reasons.clone(),
602 rollback_reasons: strict_policy.rollback_reasons.clone(),
603 },
604 ),
605 Vec::new(),
606 Vec::new(),
607 )
608}
609
610fn explain_tree_shake(
611 bundle: &ClosedWorldBundleV0,
612 symbol_kind: OmenaQueryExplainSymbolKindV0,
613 symbol_name: &str,
614) -> OmenaQueryExplainResponseV0 {
615 let reachable = flat_symbol_is_reachable(bundle, symbol_kind, symbol_name);
616 let reference = OmenaQueryExplainFactReferenceV0::ClosedWorldReachability {
617 closure_hash: bundle.closure_hash().to_string(),
618 symbol_kind,
619 symbol_name: symbol_name.to_string(),
620 guarantee: GuaranteeKindV0::NotClaimedExactTraversal,
621 };
622 OmenaQueryExplainResponseV0::new(
623 OmenaQueryExplainTargetV0::TreeShake {
624 symbol_kind,
625 symbol_name: symbol_name.to_string(),
626 },
627 OmenaQueryExplainAvailabilityV0::Available,
628 OmenaQueryExplainFactV0::new(
629 reference.clone(),
630 OmenaQueryExplainFactValueV0::ReachabilityMembership { reachable },
631 ),
632 vec![OmenaQueryExplainFactV0::new(
633 reference,
634 OmenaQueryExplainFactValueV0::ProvenanceLabel {
635 label: "moduleOwnershipUnobserved".to_string(),
636 },
637 )],
638 Vec::new(),
639 )
640}
641
642fn flat_symbol_is_reachable(
643 bundle: &ClosedWorldBundleV0,
644 symbol_kind: OmenaQueryExplainSymbolKindV0,
645 symbol_name: &str,
646) -> bool {
647 match symbol_kind {
648 OmenaQueryExplainSymbolKindV0::Class => bundle.reachability().class_names(),
649 OmenaQueryExplainSymbolKindV0::Keyframes => bundle.reachability().keyframe_names(),
650 OmenaQueryExplainSymbolKindV0::Value => bundle.reachability().value_names(),
651 OmenaQueryExplainSymbolKindV0::CustomProperty => {
652 bundle.reachability().custom_property_names()
653 }
654 }
655 .iter()
656 .any(|candidate| candidate == symbol_name)
657}
658
659fn module_symbol_is_reachable(
660 symbols: &ModuleQualifiedSymbolSetV0,
661 symbol_kind: OmenaQueryExplainSymbolKindV0,
662 symbol_name: &str,
663) -> bool {
664 match symbol_kind {
665 OmenaQueryExplainSymbolKindV0::Class => symbols.class_names(),
666 OmenaQueryExplainSymbolKindV0::Keyframes => symbols.keyframe_names(),
667 OmenaQueryExplainSymbolKindV0::Value => symbols.value_names(),
668 OmenaQueryExplainSymbolKindV0::CustomProperty => symbols.custom_property_names(),
669 }
670 .iter()
671 .any(|candidate| candidate == symbol_name)
672}
673
674pub fn explain_omena_query_tree_shake_unavailable(
675 symbol_kind: OmenaQueryExplainSymbolKindV0,
676 symbol_name: &str,
677) -> OmenaQueryExplainResponseV0 {
678 OmenaQueryExplainResponseV0::new(
679 OmenaQueryExplainTargetV0::TreeShake {
680 symbol_kind,
681 symbol_name: symbol_name.to_string(),
682 },
683 OmenaQueryExplainAvailabilityV0::NotYetAvailable,
684 OmenaQueryExplainFactV0::new(
685 OmenaQueryExplainFactReferenceV0::CapabilityGate {
686 capability: OmenaQueryExplainCapabilityV0::TreeShake,
687 },
688 OmenaQueryExplainFactValueV0::CapabilityAvailability {
689 availability: OmenaQueryExplainAvailabilityV0::NotYetAvailable,
690 },
691 ),
692 Vec::new(),
693 Vec::new(),
694 )
695}
696
697fn explain_precision(
698 precision_reference: &OmenaQuerySourcePrecisionReferenceV0,
699) -> OmenaQueryExplainResponseV0 {
700 let reference = OmenaQueryExplainFactReferenceV0::PrecisionFact {
701 source_path: precision_reference.source_path.clone(),
702 variable_name: precision_reference.variable_name.clone(),
703 reference_byte_offset: precision_reference.reference_byte_offset,
704 };
705 OmenaQueryExplainResponseV0::new(
706 OmenaQueryExplainTargetV0::Precision {
707 source_path: precision_reference.source_path.clone(),
708 variable_name: precision_reference.variable_name.clone(),
709 reference_byte_offset: precision_reference.reference_byte_offset,
710 },
711 OmenaQueryExplainAvailabilityV0::Available,
712 OmenaQueryExplainFactV0::new(
713 reference,
714 OmenaQueryExplainFactValueV0::PrecisionClassification {
715 precision: fact_precision_from_analysis_precision(&precision_reference.precision),
716 resolved_tier: precision_reference.resolved_tier.to_string(),
717 },
718 ),
719 Vec::new(),
720 Vec::new(),
721 )
722}
723
724fn explain_class_site(value: &OmenaQueryClassSiteValueV0) -> OmenaQueryExplainResponseV0 {
725 let reference = OmenaQueryExplainFactReferenceV0::ClassSiteFact {
726 source_path: value.source_path.clone(),
727 site_byte_span: value.site_byte_span,
728 };
729 OmenaQueryExplainResponseV0::new(
730 OmenaQueryExplainTargetV0::ClassSite {
731 source_path: value.source_path.clone(),
732 site_byte_span: value.site_byte_span,
733 },
734 OmenaQueryExplainAvailabilityV0::Available,
735 OmenaQueryExplainFactV0::new(
736 reference,
737 OmenaQueryExplainFactValueV0::ClassSiteValue {
738 value: Box::new(value.clone()),
739 },
740 ),
741 Vec::new(),
742 Vec::new(),
743 )
744}
745
746fn explain_cascade(result: &OmenaQueryCascadeAtPositionV0) -> OmenaQueryExplainResponseV0 {
747 let reference = OmenaQueryExplainFactReferenceV0::CascadeResolution {
748 style_path: result.style_path.clone(),
749 position: result.query_position,
750 winner_range: result.winner_declaration_range,
751 };
752 let related_spans = result
753 .winner_declaration_range
754 .map(|range| {
755 vec![OmenaQueryExplainSourceSpanV0::new(
756 result
757 .winner_declaration_file_path
758 .as_deref()
759 .unwrap_or(result.style_path.as_str()),
760 range,
761 reference.clone(),
762 )]
763 })
764 .unwrap_or_default();
765 OmenaQueryExplainResponseV0::new(
766 OmenaQueryExplainTargetV0::Cascade {
767 style_path: result.style_path.clone(),
768 position: result.query_position,
769 },
770 OmenaQueryExplainAvailabilityV0::Available,
771 OmenaQueryExplainFactV0::new(
772 reference,
773 OmenaQueryExplainFactValueV0::CascadeResolution {
774 status: result.status.to_string(),
775 candidate_count: result.candidate_declaration_count,
776 winner_source_order: result.winner_declaration_source_order,
777 },
778 ),
779 Vec::new(),
780 related_spans,
781 )
782}
783
784fn explain_bundle_unavailable(chunk_reference: &str) -> OmenaQueryExplainResponseV0 {
785 OmenaQueryExplainResponseV0::new(
786 OmenaQueryExplainTargetV0::Bundle {
787 chunk_reference: chunk_reference.to_string(),
788 },
789 OmenaQueryExplainAvailabilityV0::NotYetAvailable,
790 OmenaQueryExplainFactV0::new(
791 OmenaQueryExplainFactReferenceV0::CapabilityGate {
792 capability: OmenaQueryExplainCapabilityV0::Bundle,
793 },
794 OmenaQueryExplainFactValueV0::CapabilityAvailability {
795 availability: OmenaQueryExplainAvailabilityV0::NotYetAvailable,
796 },
797 ),
798 Vec::new(),
799 Vec::new(),
800 )
801}
802
803fn explain_hover_trace(
804 document_uri: &str,
805 position: Option<ParserPositionV0>,
806 reason_code: &str,
807 matched: bool,
808 candidate_count: usize,
809 definition_count: usize,
810) -> OmenaQueryExplainResponseV0 {
811 OmenaQueryExplainResponseV0::new(
812 OmenaQueryExplainTargetV0::HoverTrace {
813 document_uri: document_uri.to_string(),
814 position,
815 },
816 OmenaQueryExplainAvailabilityV0::Available,
817 OmenaQueryExplainFactV0::new(
818 OmenaQueryExplainFactReferenceV0::HoverResolution {
819 document_uri: document_uri.to_string(),
820 position,
821 reason_code: reason_code.to_string(),
822 },
823 OmenaQueryExplainFactValueV0::HoverResolution {
824 matched,
825 candidate_count,
826 definition_count,
827 },
828 ),
829 Vec::new(),
830 Vec::new(),
831 )
832}
833
834#[cfg(test)]
835mod tests {
836 use omena_query_transform_runner::TransformCascadeEnvironmentV0;
837
838 use super::*;
839
840 #[test]
841 fn transform_explanation_references_the_production_outcome_evidence_key() {
842 let execution = crate::execute_omena_query_transform_passes_from_source(
843 "fixture.css",
844 ".button {}",
845 &["print-css".to_string()],
846 );
847 let decision = &execution.execution.decisions[0];
848 let outcome = decision.compatibility_outcome();
849 let response = explain_omena_query(OmenaQueryExplainInputV0::Transform {
850 decision,
851 decision_ordinal: 0,
852 });
853
854 assert_eq!(
855 response.availability(),
856 OmenaQueryExplainAvailabilityV0::Available
857 );
858 assert!(matches!(
859 response.primary_fact().reference(),
860 OmenaQueryExplainFactReferenceV0::TransformOutcome {
861 evidence_node_key,
862 ..
863 } if evidence_node_key == &outcome.evidence_node_key()
864 ));
865 }
866
867 #[test]
868 fn transform_explanation_surfaces_strict_policy_counts_and_reasons() {
869 let execution =
870 crate::execute_omena_query_consumer_build_style_source_with_context_and_options(
871 "fixture.css",
872 ".card { color: red; } .card { background: blue; }",
873 &["rule-merging".to_string()],
874 &TransformExecutionContextV0::default(),
875 &crate::OmenaQueryConsumerBuildOptionsV0 {
876 verification_profile: crate::OmenaQueryBuildVerificationProfileV0::Strict,
877 ..crate::OmenaQueryConsumerBuildOptionsV0::default()
878 },
879 );
880 let decision = &execution.execution.decisions[0];
881 let response = explain_omena_query(OmenaQueryExplainInputV0::TransformWithPolicy {
882 decision,
883 decision_ordinal: 0,
884 strict_policy: &execution.execution.strict_policy,
885 });
886
887 assert!(matches!(
888 response.primary_fact().value(),
889 OmenaQueryExplainFactValueV0::TransformDecision {
890 refused_count: 1,
891 rolled_back_count: 0,
892 refusal_reasons,
893 rollback_reasons,
894 ..
895 } if refusal_reasons.len() == 1 && rollback_reasons.is_empty()
896 ));
897 }
898
899 #[test]
900 fn transform_explanation_carries_the_typed_semantic_trust_tier() {
901 let execution = crate::execute_omena_query_transform_passes_from_source_with_context(
902 "fixture.css",
903 ".card { color: red; } .card { background: blue; }",
904 &["rule-merging".to_string()],
905 &TransformExecutionContextV0 {
906 cascade_environment: Some(TransformCascadeEnvironmentV0::default()),
907 ..TransformExecutionContextV0::default()
908 },
909 );
910 let decision = &execution.execution.decisions[0];
911 let response = explain_omena_query(OmenaQueryExplainInputV0::Transform {
912 decision,
913 decision_ordinal: 0,
914 });
915
916 assert!(matches!(
917 response.primary_fact().value(),
918 OmenaQueryExplainFactValueV0::TransformDecision {
919 semantic_guarantee_tier: Some(
920 TransformSemanticGuaranteeTierV0::WinnerEqualityObserved { axes }
921 ),
922 ..
923 } if !axes.is_empty()
924 ));
925 }
926
927 #[test]
928 fn transform_explanation_preserves_typed_trust_absence() {
929 let execution = crate::execute_omena_query_transform_passes_from_source(
930 "fixture.css",
931 "@scope (.root) { .unused {} }",
932 &["empty-rule-removal".to_string()],
933 );
934 let decision = &execution.execution.decisions[0];
935 let response = explain_omena_query(OmenaQueryExplainInputV0::Transform {
936 decision,
937 decision_ordinal: 0,
938 });
939
940 assert!(matches!(
941 response.primary_fact().value(),
942 OmenaQueryExplainFactValueV0::TransformDecision {
943 semantic_guarantee_tier: Some(TransformSemanticGuaranteeTierV0::Absent { reasons }),
944 ..
945 } if !reasons.is_empty()
946 ));
947 }
948
949 #[test]
950 fn tree_shake_explanation_inherits_the_non_exact_traversal_guarantee() -> Result<(), String> {
951 let context = TransformExecutionContextV0 {
952 reachable_class_names: vec!["button".to_string()],
953 ..TransformExecutionContextV0::default()
954 };
955 let response = explain_omena_query_tree_shake_for_style_source(
956 "src/App.module.css",
957 ".button { color: red; }",
958 &context,
959 OmenaQueryExplainSymbolKindV0::Class,
960 "button",
961 )
962 .ok_or_else(|| "linked closed-world fixture should be available".to_string())?;
963
964 assert!(matches!(
965 response.primary_fact().reference(),
966 OmenaQueryExplainFactReferenceV0::ClosedWorldReachability {
967 guarantee: GuaranteeKindV0::NotClaimedExactTraversal,
968 ..
969 }
970 ));
971 assert!(matches!(
972 response.primary_fact().value(),
973 OmenaQueryExplainFactValueV0::ReachabilityMembership { reachable: true }
974 ));
975 Ok(())
976 }
977
978 #[test]
979 fn unavailable_bundle_explanation_is_still_fact_backed() {
980 let response = explain_omena_query(OmenaQueryExplainInputV0::BundleUnavailable {
981 chunk_reference: "main",
982 });
983 assert_eq!(
984 response.availability(),
985 OmenaQueryExplainAvailabilityV0::NotYetAvailable
986 );
987 assert!(matches!(
988 response.primary_fact().reference(),
989 OmenaQueryExplainFactReferenceV0::CapabilityGate {
990 capability: OmenaQueryExplainCapabilityV0::Bundle
991 }
992 ));
993 }
994
995 #[test]
996 fn diagnostic_explanation_reuses_product_diagnostic_provenance() -> Result<(), String> {
997 let diagnostics = crate::summarize_omena_query_style_diagnostics_for_file(
998 "file:///fixture.scss",
999 "@import 'legacy';",
1000 &[],
1001 );
1002 let diagnostic = diagnostics
1003 .diagnostics
1004 .first()
1005 .ok_or_else(|| "fixture should produce a Sass import diagnostic".to_string())?;
1006 let response = explain_omena_query(OmenaQueryExplainInputV0::Diagnostic {
1007 style_path: "file:///fixture.scss",
1008 diagnostic,
1009 });
1010
1011 assert_eq!(
1012 response.supporting_facts().len(),
1013 diagnostic.provenance.len()
1014 );
1015 assert!(matches!(
1016 response.primary_fact().reference(),
1017 OmenaQueryExplainFactReferenceV0::Diagnostic {
1018 code,
1019 evidence_node_key,
1020 ..
1021 } if code == diagnostic.code
1022 && evidence_node_key == &EvidenceNodeKeyV0::new("diagnosticProvenance", diagnostic.code)
1023 ));
1024 assert_eq!(response.related_spans().len(), 1);
1025 Ok(())
1026 }
1027
1028 #[test]
1029 fn precision_explanation_uses_the_authoritative_precision_adapter() -> Result<(), String> {
1030 let source = "const className = 'button';\nclassName;";
1031 let reference_byte_offset = source
1032 .rfind("className")
1033 .ok_or_else(|| "fixture reference should exist".to_string())?;
1034 let reference = crate::resolve_omena_query_source_precision_for_source(
1035 "fixture.ts",
1036 source,
1037 Some("typescript"),
1038 "className",
1039 reference_byte_offset,
1040 );
1041 let response = explain_omena_query(OmenaQueryExplainInputV0::Precision {
1042 reference: &reference,
1043 });
1044
1045 assert!(matches!(
1046 response.primary_fact().value(),
1047 OmenaQueryExplainFactValueV0::PrecisionClassification {
1048 precision: FactPrecision::Conservative,
1049 ..
1050 }
1051 ));
1052 Ok(())
1053 }
1054}