1use engine_input_producers::{
2 EngineInputV2, ExpressionSemanticsCanonicalProducerSignalV0,
3 ExpressionSemanticsQueryFragmentsV0, SelectorUsageCanonicalProducerSignalV0,
4 SelectorUsageQueryFragmentsV0, SourceResolutionCanonicalProducerSignalV0,
5 SourceResolutionQueryFragmentsV0,
6 summarize_expression_semantics_canonical_producer_signal_input,
7 summarize_expression_semantics_query_fragments_input,
8 summarize_selector_usage_canonical_producer_signal_input,
9 summarize_selector_usage_query_fragments_input,
10};
11use omena_abstract_value::{AbstractValueDomainSummaryV0, summarize_omena_abstract_value_domain};
12use omena_bridge::{
13 StyleSemanticGraphSummaryV0, summarize_omena_bridge_style_semantic_graph_from_source,
14};
15use omena_resolver::{
16 summarize_omena_resolver_canonical_producer_signal, summarize_omena_resolver_query_fragments,
17};
18use serde::Serialize;
19
20#[derive(Debug, Serialize)]
21#[serde(rename_all = "camelCase")]
22pub struct OmenaQueryBoundarySummaryV0 {
23 pub schema_version: &'static str,
24 pub product: &'static str,
25 pub query_engine_name: &'static str,
26 pub input_version: String,
27 pub abstract_value_domain: AbstractValueDomainSummaryV0,
28 pub selected_query_adapter_capabilities: SelectedQueryAdapterCapabilitiesV0,
29 pub delegated_fragment_products: Vec<&'static str>,
30 pub expression_semantics_query_count: usize,
31 pub source_resolution_query_count: usize,
32 pub selector_usage_query_count: usize,
33 pub total_query_count: usize,
34 pub ready_surfaces: Vec<&'static str>,
35 pub cme_coupled_surfaces: Vec<&'static str>,
36 pub next_decoupling_targets: Vec<&'static str>,
37}
38
39#[derive(Debug, Serialize)]
40#[serde(rename_all = "camelCase")]
41pub struct OmenaQueryFragmentBundleV0 {
42 pub schema_version: &'static str,
43 pub product: &'static str,
44 pub input_version: String,
45 pub expression_semantics: ExpressionSemanticsQueryFragmentsV0,
46 pub source_resolution: SourceResolutionQueryFragmentsV0,
47 pub selector_usage: SelectorUsageQueryFragmentsV0,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
51#[serde(rename_all = "camelCase")]
52pub struct SelectedQueryAdapterCapabilitiesV0 {
53 pub schema_version: &'static str,
54 pub product: &'static str,
55 pub default_candidate_backend: &'static str,
56 pub backend_kinds: Vec<SelectedQueryBackendCapabilityV0>,
57 pub runner_commands: Vec<SelectedQueryRunnerCommandV0>,
58 pub required_input_contracts: Vec<&'static str>,
59 pub adapter_readiness: Vec<&'static str>,
60 pub routing_status: &'static str,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
64#[serde(rename_all = "camelCase")]
65pub struct SelectedQueryBackendCapabilityV0 {
66 pub backend_kind: &'static str,
67 pub source_resolution: bool,
68 pub expression_semantics: bool,
69 pub selector_usage: bool,
70 pub style_semantic_graph: bool,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
74#[serde(rename_all = "camelCase")]
75pub struct SelectedQueryRunnerCommandV0 {
76 pub surface: &'static str,
77 pub command: &'static str,
78 pub input_contract: &'static str,
79 pub output_product: &'static str,
80}
81
82#[derive(Debug, Serialize)]
83#[serde(rename_all = "camelCase")]
84pub struct OmenaQueryStyleSemanticGraphBatchOutputV0 {
85 pub schema_version: &'static str,
86 pub product: &'static str,
87 pub graphs: Vec<OmenaQueryStyleSemanticGraphBatchEntryV0>,
88}
89
90#[derive(Debug, Serialize)]
91#[serde(rename_all = "camelCase")]
92pub struct OmenaQueryStyleSemanticGraphBatchEntryV0 {
93 pub style_path: String,
94 pub graph: Option<StyleSemanticGraphSummaryV0>,
95}
96
97pub fn summarize_omena_query_boundary(input: &EngineInputV2) -> OmenaQueryBoundarySummaryV0 {
98 let fragment_bundle = summarize_omena_query_fragment_bundle(input);
99 let expression_semantics_query_count = fragment_bundle.expression_semantics.fragments.len();
100 let source_resolution_query_count = fragment_bundle.source_resolution.fragments.len();
101 let selector_usage_query_count = fragment_bundle.selector_usage.fragments.len();
102
103 OmenaQueryBoundarySummaryV0 {
104 schema_version: "0",
105 product: "omena-query.boundary",
106 query_engine_name: "omena-query",
107 input_version: input.version.clone(),
108 abstract_value_domain: summarize_omena_abstract_value_domain(),
109 selected_query_adapter_capabilities:
110 summarize_omena_query_selected_query_adapter_capabilities(),
111 delegated_fragment_products: vec![
112 "engine-input-producers.expression-semantics-query-fragments",
113 "engine-input-producers.source-resolution-query-fragments",
114 "omena-resolver.boundary",
115 "engine-input-producers.selector-usage-query-fragments",
116 ],
117 expression_semantics_query_count,
118 source_resolution_query_count,
119 selector_usage_query_count,
120 total_query_count: expression_semantics_query_count
121 + source_resolution_query_count
122 + selector_usage_query_count,
123 ready_surfaces: vec![
124 "queryFragmentBundle",
125 "abstractValueProjectionContract",
126 "sourceResolutionResolverBoundary",
127 "queryBoundarySummary",
128 ],
129 cme_coupled_surfaces: vec!["EngineInputV2", "producerQueryFragments"],
130 next_decoupling_targets: vec!["queryEvaluationRuntime", "selectedQueryBackendAdapter"],
131 }
132}
133
134pub fn summarize_omena_query_selected_query_adapter_capabilities()
135-> SelectedQueryAdapterCapabilitiesV0 {
136 SelectedQueryAdapterCapabilitiesV0 {
137 schema_version: "0",
138 product: "omena-query.selected-query-adapter-capabilities",
139 default_candidate_backend: "rust-selected-query",
140 backend_kinds: vec![
141 SelectedQueryBackendCapabilityV0 {
142 backend_kind: "typescript-current",
143 source_resolution: false,
144 expression_semantics: false,
145 selector_usage: false,
146 style_semantic_graph: false,
147 },
148 SelectedQueryBackendCapabilityV0 {
149 backend_kind: "rust-source-resolution",
150 source_resolution: true,
151 expression_semantics: false,
152 selector_usage: false,
153 style_semantic_graph: false,
154 },
155 SelectedQueryBackendCapabilityV0 {
156 backend_kind: "rust-expression-semantics",
157 source_resolution: false,
158 expression_semantics: true,
159 selector_usage: false,
160 style_semantic_graph: false,
161 },
162 SelectedQueryBackendCapabilityV0 {
163 backend_kind: "rust-selector-usage",
164 source_resolution: false,
165 expression_semantics: false,
166 selector_usage: true,
167 style_semantic_graph: false,
168 },
169 SelectedQueryBackendCapabilityV0 {
170 backend_kind: "rust-selected-query",
171 source_resolution: true,
172 expression_semantics: true,
173 selector_usage: true,
174 style_semantic_graph: true,
175 },
176 ],
177 runner_commands: vec![
178 SelectedQueryRunnerCommandV0 {
179 surface: "sourceResolution",
180 command: "input-source-resolution-canonical-producer",
181 input_contract: "EngineInputV2",
182 output_product: "engine-input-producers.source-resolution-canonical-producer",
183 },
184 SelectedQueryRunnerCommandV0 {
185 surface: "expressionSemantics",
186 command: "input-expression-semantics-canonical-producer",
187 input_contract: "EngineInputV2",
188 output_product: "engine-input-producers.expression-semantics-canonical-producer",
189 },
190 SelectedQueryRunnerCommandV0 {
191 surface: "selectorUsage",
192 command: "input-selector-usage-canonical-producer",
193 input_contract: "EngineInputV2",
194 output_product: "engine-input-producers.selector-usage-canonical-producer",
195 },
196 SelectedQueryRunnerCommandV0 {
197 surface: "styleSemanticGraph",
198 command: "style-semantic-graph",
199 input_contract: "StyleSemanticGraphInputV0",
200 output_product: "omena-semantic.style-semantic-graph",
201 },
202 SelectedQueryRunnerCommandV0 {
203 surface: "styleSemanticGraphBatch",
204 command: "style-semantic-graph-batch",
205 input_contract: "StyleSemanticGraphBatchInputV0",
206 output_product: "omena-semantic.style-semantic-graph-batch",
207 },
208 ],
209 required_input_contracts: vec![
210 "EngineInputV2",
211 "StyleSemanticGraphInputV0",
212 "StyleSemanticGraphBatchInputV0",
213 ],
214 adapter_readiness: vec![
215 "backendCapabilityMatrix",
216 "canonicalProducerWrapperBoundary",
217 "styleSemanticGraphBridgeBoundary",
218 "runnerCommandContract",
219 "fragmentBundleBoundary",
220 ],
221 routing_status: "declaredOnly",
222 }
223}
224
225pub fn summarize_omena_query_fragment_bundle(input: &EngineInputV2) -> OmenaQueryFragmentBundleV0 {
226 OmenaQueryFragmentBundleV0 {
227 schema_version: "0",
228 product: "omena-query.fragment-bundle",
229 input_version: input.version.clone(),
230 expression_semantics: summarize_omena_query_expression_semantics_query_fragments(input),
231 source_resolution: summarize_omena_query_source_resolution_query_fragments(input),
232 selector_usage: summarize_omena_query_selector_usage_query_fragments(input),
233 }
234}
235
236pub fn summarize_omena_query_expression_semantics_query_fragments(
237 input: &EngineInputV2,
238) -> ExpressionSemanticsQueryFragmentsV0 {
239 summarize_expression_semantics_query_fragments_input(input)
240}
241
242pub fn summarize_omena_query_source_resolution_query_fragments(
243 input: &EngineInputV2,
244) -> SourceResolutionQueryFragmentsV0 {
245 summarize_omena_resolver_query_fragments(input)
246}
247
248pub fn summarize_omena_query_selector_usage_query_fragments(
249 input: &EngineInputV2,
250) -> SelectorUsageQueryFragmentsV0 {
251 summarize_selector_usage_query_fragments_input(input)
252}
253
254pub fn summarize_omena_query_source_resolution_canonical_producer_signal(
255 input: &EngineInputV2,
256) -> SourceResolutionCanonicalProducerSignalV0 {
257 summarize_omena_resolver_canonical_producer_signal(input)
258}
259
260pub fn summarize_omena_query_expression_semantics_canonical_producer_signal(
261 input: &EngineInputV2,
262) -> ExpressionSemanticsCanonicalProducerSignalV0 {
263 summarize_expression_semantics_canonical_producer_signal_input(input)
264}
265
266pub fn summarize_omena_query_selector_usage_canonical_producer_signal(
267 input: &EngineInputV2,
268) -> SelectorUsageCanonicalProducerSignalV0 {
269 summarize_selector_usage_canonical_producer_signal_input(input)
270}
271
272pub fn summarize_omena_query_style_semantic_graph_from_source(
273 style_path: &str,
274 style_source: &str,
275 input: &EngineInputV2,
276) -> Option<StyleSemanticGraphSummaryV0> {
277 summarize_omena_bridge_style_semantic_graph_from_source(style_path, style_source, input)
278}
279
280pub fn summarize_omena_query_style_semantic_graph_batch_from_sources<'a>(
281 styles: impl IntoIterator<Item = (&'a str, &'a str)>,
282 input: &EngineInputV2,
283) -> OmenaQueryStyleSemanticGraphBatchOutputV0 {
284 let graphs = styles
285 .into_iter()
286 .map(
287 |(style_path, style_source)| OmenaQueryStyleSemanticGraphBatchEntryV0 {
288 style_path: style_path.to_string(),
289 graph: summarize_omena_query_style_semantic_graph_from_source(
290 style_path,
291 style_source,
292 input,
293 ),
294 },
295 )
296 .collect::<Vec<_>>();
297
298 OmenaQueryStyleSemanticGraphBatchOutputV0 {
299 schema_version: "0",
300 product: "omena-semantic.style-semantic-graph-batch",
301 graphs,
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use engine_input_producers::{
308 ClassExpressionInputV2, EngineInputV2, PositionV2, RangeV2, SourceAnalysisInputV2,
309 SourceDocumentV2, StringTypeFactsV2, StyleAnalysisInputV2, StyleDocumentV2,
310 StyleSelectorV2, TypeFactEntryV2,
311 };
312
313 use super::{
314 SelectedQueryAdapterCapabilitiesV0, summarize_omena_query_boundary,
315 summarize_omena_query_expression_semantics_canonical_producer_signal,
316 summarize_omena_query_expression_semantics_query_fragments,
317 summarize_omena_query_fragment_bundle,
318 summarize_omena_query_selected_query_adapter_capabilities,
319 summarize_omena_query_selector_usage_canonical_producer_signal,
320 summarize_omena_query_selector_usage_query_fragments,
321 summarize_omena_query_source_resolution_canonical_producer_signal,
322 summarize_omena_query_source_resolution_query_fragments,
323 summarize_omena_query_style_semantic_graph_batch_from_sources,
324 summarize_omena_query_style_semantic_graph_from_source,
325 };
326
327 #[test]
328 fn summarizes_query_boundary_over_producer_fragments() {
329 let input = sample_input();
330 let summary = summarize_omena_query_boundary(&input);
331
332 assert_eq!(summary.schema_version, "0");
333 assert_eq!(summary.product, "omena-query.boundary");
334 assert_eq!(summary.query_engine_name, "omena-query");
335 assert_eq!(summary.input_version, "2");
336 assert_eq!(
337 summary.abstract_value_domain.product,
338 "omena-abstract-value.domain"
339 );
340 assert_eq!(
341 summary.selected_query_adapter_capabilities.product,
342 "omena-query.selected-query-adapter-capabilities"
343 );
344 assert_eq!(summary.expression_semantics_query_count, 2);
345 assert_eq!(summary.source_resolution_query_count, 2);
346 assert_eq!(summary.selector_usage_query_count, 2);
347 assert_eq!(summary.total_query_count, 6);
348 assert!(
349 summary
350 .ready_surfaces
351 .contains(&"abstractValueProjectionContract")
352 );
353 assert!(
354 summary
355 .ready_surfaces
356 .contains(&"sourceResolutionResolverBoundary")
357 );
358 assert!(
359 summary
360 .delegated_fragment_products
361 .contains(&"omena-resolver.boundary")
362 );
363 assert!(
364 summary
365 .cme_coupled_surfaces
366 .contains(&"producerQueryFragments")
367 );
368 }
369
370 #[test]
371 fn bundles_expression_source_and_selector_query_fragments() {
372 let input = sample_input();
373 let bundle = summarize_omena_query_fragment_bundle(&input);
374
375 assert_eq!(bundle.schema_version, "0");
376 assert_eq!(bundle.product, "omena-query.fragment-bundle");
377 assert_eq!(bundle.input_version, "2");
378 assert_eq!(bundle.expression_semantics.fragments.len(), 2);
379 assert_eq!(bundle.expression_semantics.fragments[0].query_id, "expr-1");
380 assert_eq!(bundle.source_resolution.fragments.len(), 2);
381 assert_eq!(bundle.source_resolution.fragments[1].query_id, "expr-2");
382 assert_eq!(bundle.selector_usage.fragments.len(), 2);
383 assert_eq!(bundle.selector_usage.fragments[0].query_id, "btn-active");
384
385 let expression = summarize_omena_query_expression_semantics_query_fragments(&input);
386 let source = summarize_omena_query_source_resolution_query_fragments(&input);
387 let selector = summarize_omena_query_selector_usage_query_fragments(&input);
388
389 assert_eq!(expression.schema_version, "0");
390 assert_eq!(source.schema_version, "0");
391 assert_eq!(selector.schema_version, "0");
392 assert_eq!(expression.input_version, "2");
393 assert_eq!(source.input_version, "2");
394 assert_eq!(selector.input_version, "2");
395 assert_eq!(
396 expression.fragments.len(),
397 bundle.expression_semantics.fragments.len()
398 );
399 assert_eq!(
400 source.fragments.len(),
401 bundle.source_resolution.fragments.len()
402 );
403 assert_eq!(
404 selector.fragments.len(),
405 bundle.selector_usage.fragments.len()
406 );
407 }
408
409 #[test]
410 fn declares_selected_query_adapter_capabilities_without_flipping_runtime_routing() {
411 let summary = summarize_omena_query_selected_query_adapter_capabilities();
412
413 assert_eq!(summary.schema_version, "0");
414 assert_eq!(
415 summary.product,
416 "omena-query.selected-query-adapter-capabilities"
417 );
418 assert_eq!(summary.default_candidate_backend, "rust-selected-query");
419 assert_eq!(summary.routing_status, "declaredOnly");
420
421 let unified = backend(&summary, "rust-selected-query");
422 assert!(unified.is_some());
423 let Some(unified) = unified else {
424 return;
425 };
426 assert!(unified.source_resolution);
427 assert!(unified.expression_semantics);
428 assert!(unified.selector_usage);
429 assert!(unified.style_semantic_graph);
430
431 let source_only = backend(&summary, "rust-source-resolution");
432 assert!(source_only.is_some());
433 let Some(source_only) = source_only else {
434 return;
435 };
436 assert!(source_only.source_resolution);
437 assert!(!source_only.expression_semantics);
438 assert!(!source_only.selector_usage);
439 assert!(!source_only.style_semantic_graph);
440
441 assert!(
442 summary
443 .runner_commands
444 .iter()
445 .any(|command| command.command == "style-semantic-graph-batch")
446 );
447 assert!(summary.adapter_readiness.contains(&"runnerCommandContract"));
448 assert!(
449 summary
450 .adapter_readiness
451 .contains(&"canonicalProducerWrapperBoundary")
452 );
453 assert!(
454 summary
455 .adapter_readiness
456 .contains(&"styleSemanticGraphBridgeBoundary")
457 );
458 }
459
460 #[test]
461 fn owns_selected_query_canonical_producer_wrappers_without_changing_products() {
462 let input = sample_input();
463
464 let source = summarize_omena_query_source_resolution_canonical_producer_signal(&input);
465 assert_eq!(source.schema_version, "0");
466 assert_eq!(source.input_version, "2");
467 assert_eq!(source.canonical_bundle.query_fragments.len(), 2);
468 assert_eq!(source.evaluator_candidates.results.len(), 2);
469
470 let expression =
471 summarize_omena_query_expression_semantics_canonical_producer_signal(&input);
472 assert_eq!(expression.schema_version, "0");
473 assert_eq!(expression.input_version, "2");
474 assert_eq!(expression.canonical_bundle.query_fragments.len(), 2);
475 assert_eq!(expression.evaluator_candidates.results.len(), 2);
476
477 let selector = summarize_omena_query_selector_usage_canonical_producer_signal(&input);
478 assert_eq!(selector.schema_version, "0");
479 assert_eq!(selector.input_version, "2");
480 assert_eq!(selector.canonical_bundle.query_fragments.len(), 2);
481 assert_eq!(selector.evaluator_candidates.results.len(), 2);
482 }
483
484 #[test]
485 fn owns_style_semantic_graph_adapter_boundary_without_changing_graph_product() {
486 let input = sample_input();
487 let graph = summarize_omena_query_style_semantic_graph_from_source(
488 "/tmp/App.module.scss",
489 ".btn-active { color: red; }",
490 &input,
491 );
492 assert!(graph.is_some());
493 let Some(graph) = graph else {
494 return;
495 };
496 assert_eq!(graph.schema_version, "0");
497 assert_eq!(graph.product, "omena-semantic.style-semantic-graph");
498 assert_eq!(graph.selector_identity_engine.canonical_ids.len(), 1);
499
500 let batch = summarize_omena_query_style_semantic_graph_batch_from_sources(
501 [
502 ("/tmp/App.module.scss", ".btn-active { color: red; }"),
503 ("/tmp/Card.module.scss", ".card-header { color: blue; }"),
504 ],
505 &input,
506 );
507 assert_eq!(batch.schema_version, "0");
508 assert_eq!(batch.product, "omena-semantic.style-semantic-graph-batch");
509 assert_eq!(batch.graphs.len(), 2);
510 assert_eq!(batch.graphs[0].style_path, "/tmp/App.module.scss");
511 assert!(batch.graphs[0].graph.is_some());
512 assert!(batch.graphs[1].graph.is_some());
513 }
514
515 fn backend<'a>(
516 summary: &'a SelectedQueryAdapterCapabilitiesV0,
517 backend_kind: &str,
518 ) -> Option<&'a super::SelectedQueryBackendCapabilityV0> {
519 summary
520 .backend_kinds
521 .iter()
522 .find(|backend| backend.backend_kind == backend_kind)
523 }
524
525 fn sample_input() -> EngineInputV2 {
526 EngineInputV2 {
527 version: "2".to_string(),
528 sources: vec![SourceAnalysisInputV2 {
529 document: SourceDocumentV2 {
530 class_expressions: vec![
531 ClassExpressionInputV2 {
532 id: "expr-1".to_string(),
533 kind: "symbolRef".to_string(),
534 scss_module_path: "/tmp/App.module.scss".to_string(),
535 range: range(4, 12, 4, 16),
536 class_name: None,
537 root_binding_decl_id: Some("decl-1".to_string()),
538 access_path: None,
539 },
540 ClassExpressionInputV2 {
541 id: "expr-2".to_string(),
542 kind: "styleAccess".to_string(),
543 scss_module_path: "/tmp/Card.module.scss".to_string(),
544 range: range(6, 9, 6, 20),
545 class_name: Some("card-header".to_string()),
546 root_binding_decl_id: None,
547 access_path: Some(vec!["card".to_string(), "header".to_string()]),
548 },
549 ],
550 },
551 }],
552 styles: vec![
553 StyleAnalysisInputV2 {
554 file_path: "/tmp/App.module.scss".to_string(),
555 document: StyleDocumentV2 {
556 selectors: vec![StyleSelectorV2 {
557 name: "btn-active".to_string(),
558 view_kind: "canonical".to_string(),
559 canonical_name: Some("btn-active".to_string()),
560 range: range(1, 1, 1, 12),
561 nested_safety: Some("safe".to_string()),
562 composes: None,
563 bem_suffix: None,
564 }],
565 },
566 },
567 StyleAnalysisInputV2 {
568 file_path: "/tmp/Card.module.scss".to_string(),
569 document: StyleDocumentV2 {
570 selectors: vec![StyleSelectorV2 {
571 name: "card-header".to_string(),
572 view_kind: "canonical".to_string(),
573 canonical_name: Some("card-header".to_string()),
574 range: range(3, 1, 3, 13),
575 nested_safety: Some("unsafe".to_string()),
576 composes: None,
577 bem_suffix: None,
578 }],
579 },
580 },
581 ],
582 type_facts: vec![
583 TypeFactEntryV2 {
584 file_path: "/tmp/App.tsx".to_string(),
585 expression_id: "expr-1".to_string(),
586 facts: StringTypeFactsV2 {
587 kind: "constrained".to_string(),
588 constraint_kind: Some("prefixSuffix".to_string()),
589 values: None,
590 prefix: Some("btn-".to_string()),
591 suffix: Some("-active".to_string()),
592 min_len: Some(10),
593 max_len: None,
594 char_must: None,
595 char_may: None,
596 may_include_other_chars: None,
597 },
598 },
599 TypeFactEntryV2 {
600 file_path: "/tmp/Card.tsx".to_string(),
601 expression_id: "expr-2".to_string(),
602 facts: StringTypeFactsV2 {
603 kind: "finiteSet".to_string(),
604 constraint_kind: None,
605 values: Some(vec!["card-header".to_string(), "card-body".to_string()]),
606 prefix: None,
607 suffix: None,
608 min_len: None,
609 max_len: None,
610 char_must: None,
611 char_may: None,
612 may_include_other_chars: None,
613 },
614 },
615 ],
616 }
617 }
618
619 fn range(
620 start_line: usize,
621 start_character: usize,
622 end_line: usize,
623 end_character: usize,
624 ) -> RangeV2 {
625 RangeV2 {
626 start: PositionV2 {
627 line: start_line,
628 character: start_character,
629 },
630 end: PositionV2 {
631 line: end_line,
632 character: end_character,
633 },
634 }
635 }
636}