1use std::collections::BTreeSet;
2
3use plexus_engine::{EngineCapabilities, ExprKind, OpKind};
4
5use crate::types::{ConformanceProfile, ProfileSpec, ProfileValidationResult};
6
7fn core_read_ops() -> BTreeSet<OpKind> {
10 BTreeSet::from([
11 OpKind::ScanNodes,
12 OpKind::ScanRels,
13 OpKind::Expand,
14 OpKind::Filter,
15 OpKind::Project,
16 OpKind::Aggregate,
17 OpKind::Sort,
18 OpKind::Limit,
19 OpKind::Union,
20 OpKind::Return,
21 ])
22}
23
24fn core_read_exprs() -> BTreeSet<ExprKind> {
25 BTreeSet::from([
26 ExprKind::ColRef,
27 ExprKind::PropAccess,
28 ExprKind::IntLiteral,
29 ExprKind::FloatLiteral,
30 ExprKind::BoolLiteral,
31 ExprKind::StringLiteral,
32 ExprKind::NullLiteral,
33 ExprKind::Cmp,
34 ExprKind::And,
35 ExprKind::Or,
36 ExprKind::Not,
37 ExprKind::IsNull,
38 ExprKind::IsNotNull,
39 ExprKind::StartsWith,
40 ExprKind::EndsWith,
41 ExprKind::Contains,
42 ExprKind::Agg,
43 ExprKind::Arith,
44 ExprKind::Param,
45 ExprKind::Case,
46 ])
47}
48
49pub fn profile_spec(profile: ConformanceProfile) -> ProfileSpec {
53 match profile {
54 ConformanceProfile::CoreRead => ProfileSpec {
55 name: "core-read",
56 description: "Read-only query support: MATCH/WHERE/RETURN/WITH/ORDER BY/LIMIT/SKIP \
57 with single-hop expansion, aggregation, and Phase 1 expressions.",
58 required_ops: core_read_ops(),
59 required_exprs: core_read_exprs(),
60 extends: None,
61 graduation_criteria: "Pass all TCK readonly scenario groups; no ordering regressions.",
62 },
63
64 ConformanceProfile::CoreWrite => {
65 let mut ops = core_read_ops();
66 ops.extend([
67 OpKind::CreateNode,
68 OpKind::CreateRel,
69 OpKind::Merge,
70 OpKind::Delete,
71 OpKind::SetProperty,
72 OpKind::RemoveProperty,
73 OpKind::ConstRow,
74 ]);
75 ProfileSpec {
76 name: "core-write",
77 description: "Everything in core-read plus DML mutation ops \
78 (CREATE/MERGE/DELETE/SET/REMOVE).",
79 required_ops: ops,
80 required_exprs: core_read_exprs(),
81 extends: Some(ConformanceProfile::CoreRead),
82 graduation_criteria: "Pass DML integration suite; MERGE idempotence coverage; \
83 no core-read regressions.",
84 }
85 }
86
87 ConformanceProfile::GqlSubset => {
88 let mut ops = core_read_ops();
89 ops.extend([
90 OpKind::OptionalExpand,
91 OpKind::SemiExpand,
92 OpKind::ExpandVarLen,
93 OpKind::PathConstruct,
94 OpKind::BlockMarker,
95 OpKind::Unwind,
96 ]);
97 let mut exprs = core_read_exprs();
98 exprs.extend([
99 ExprKind::In,
100 ExprKind::ListLiteral,
101 ExprKind::MapLiteral,
102 ExprKind::Exists,
103 ExprKind::ListComprehension,
104 ]);
105 ProfileSpec {
106 name: "gql-subset",
107 description: "Everything in core-read plus Phase 10 GQL-specific ops \
108 (OPTIONAL MATCH, variable-length paths, UNWIND, path construction, \
109 query-block markers) and Phase 2 expression forms \
110 (IN, list/map literals, EXISTS, list comprehensions).",
111 required_ops: ops,
112 required_exprs: exprs,
113 extends: Some(ConformanceProfile::CoreRead),
114 graduation_criteria: "Pass plexus-gql normalization and execution pipeline; \
115 no core-read regressions.",
116 }
117 }
118
119 ConformanceProfile::VectorExtension => {
120 let mut ops = core_read_ops();
121 ops.extend([OpKind::VectorScan, OpKind::Rerank]);
122 let mut exprs = core_read_exprs();
123 exprs.insert(ExprKind::VectorSimilarity);
124 ProfileSpec {
125 name: "vector-extension",
126 description: "Everything in core-read plus vector/hybrid retrieval ops \
127 (VectorScan, Rerank) and VectorSimilarity expressions.",
128 required_ops: ops,
129 required_exprs: exprs,
130 extends: Some(ConformanceProfile::CoreRead),
131 graduation_criteria:
132 "Negative (capability-rejection) cases pass without a vector engine. \
133 Positive cases require a mock vector engine (Phase 13). \
134 Graduation to core requires an external engine consumer.",
135 }
136 }
137 }
138}
139
140pub fn validate_for_profile(
147 caps: &EngineCapabilities,
148 profile: ConformanceProfile,
149) -> ProfileValidationResult {
150 let spec = profile_spec(profile);
151 let missing_ops: BTreeSet<OpKind> = spec
152 .required_ops
153 .iter()
154 .copied()
155 .filter(|op| !caps.supported_ops.contains(op))
156 .collect();
157 let missing_exprs: BTreeSet<ExprKind> = spec
158 .required_exprs
159 .iter()
160 .copied()
161 .filter(|ex| !caps.supported_exprs.contains(ex))
162 .collect();
163 let satisfied = missing_ops.is_empty() && missing_exprs.is_empty();
164 ProfileValidationResult {
165 satisfied,
166 missing_ops,
167 missing_exprs,
168 }
169}