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