Skip to main content

plexus_conformance/
profiles.rs

1use std::collections::BTreeSet;
2
3use plexus_engine::{EngineCapabilities, ExprKind, OpKind};
4
5use crate::types::{ConformanceProfile, ProfileSpec, ProfileValidationResult};
6
7// ── Shared base sets ─────────────────────────────────────────────────────────
8
9fn 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
49// ── Profile specs ─────────────────────────────────────────────────────────────
50
51/// Return the [`ProfileSpec`] for the given [`ConformanceProfile`].
52pub 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
140// ── Validation ────────────────────────────────────────────────────────────────
141
142/// Validate whether `caps` fully satisfies the requirements of `profile`.
143///
144/// Returns a [`ProfileValidationResult`] with `satisfied = true` only when
145/// neither `missing_ops` nor `missing_exprs` is non-empty.
146pub 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}