1use std::path::PathBuf;
2
3use plexus_engine::{
4 flagship_graph_rag_reference_contract, proof_fixture_graph, IndependentConsumerEngine,
5 PlanEngine, Value,
6};
7use plexus_serde::{
8 current_plan_version, CmpOp, ColDef, ColKind, Expr, LogicalType, Op, Plan, SortDir,
9};
10use serde::Serialize;
11
12#[derive(Debug, Serialize)]
13struct IndependentConsumerProofReport {
14 proof_id: String,
15 consumer: String,
16 implementation: String,
17 supported_subset: SupportedSubset,
18 reference_contract: plexus_engine::FlagshipGraphRagReferenceContract,
19 case_sources: Vec<String>,
20 total: usize,
21 passed: usize,
22 failed: usize,
23 cases: Vec<IndependentConsumerProofCase>,
24}
25
26#[derive(Debug, Serialize)]
27struct SupportedSubset {
28 ops: Vec<&'static str>,
29 exprs: Vec<&'static str>,
30 notes: Vec<&'static str>,
31}
32
33#[derive(Debug, Serialize)]
34struct IndependentConsumerProofCase {
35 case: String,
36 passed: bool,
37 error: Option<String>,
38}
39
40fn main() -> Result<(), String> {
41 let case_paths = proof_case_paths();
42 let cases = proof_cases();
43
44 let mut reports = Vec::with_capacity(cases.len());
45 let mut passed = 0usize;
46 for case in cases {
47 let mut engine = IndependentConsumerEngine::new(proof_fixture_graph());
48 let outcome = engine
49 .execute_plan(&case.plan)
50 .map_err(|err| err.to_string());
51 let assertion = match outcome {
52 Ok(result) if result.rows == case.expected_rows => Ok(()),
53 Ok(result) => Err(format!(
54 "row mismatch: expected {:?}, got {:?}",
55 case.expected_rows, result.rows
56 )),
57 Err(err) => Err(err),
58 };
59 if assertion.is_ok() {
60 passed += 1;
61 }
62 reports.push(IndependentConsumerProofCase {
63 case: case.name,
64 passed: assertion.is_ok(),
65 error: assertion.err(),
66 });
67 }
68
69 let report = IndependentConsumerProofReport {
70 proof_id: "plexus-independent-consumer-proof-v1".to_string(),
71 consumer: "independent-consumer-example".to_string(),
72 implementation: "independent PlanEngine implementation with a private flat graph store"
73 .to_string(),
74 supported_subset: SupportedSubset {
75 ops: vec![
76 "ScanNodes",
77 "Expand",
78 "OptionalExpand",
79 "Filter",
80 "Project",
81 "Sort",
82 "Return",
83 ],
84 exprs: vec![
85 "ColRef",
86 "PropAccess",
87 "IntLiteral",
88 "FloatLiteral",
89 "BoolLiteral",
90 "StringLiteral",
91 "NullLiteral",
92 "Cmp",
93 ],
94 notes: vec![
95 "This proof intentionally covers a bounded core-read subset only.",
96 "The example does not reuse the reference engine execution path.",
97 "The checked-in JSON artifact is produced by running this example.",
98 ],
99 },
100 reference_contract: flagship_graph_rag_reference_contract(),
101 case_sources: case_paths
102 .iter()
103 .map(|path| path.display().to_string())
104 .collect(),
105 total: reports.len(),
106 passed,
107 failed: reports.len().saturating_sub(passed),
108 cases: reports,
109 };
110
111 println!(
112 "{}",
113 serde_json::to_string_pretty(&report).map_err(|err| err.to_string())?
114 );
115 Ok(())
116}
117
118fn proof_case_paths() -> Vec<PathBuf> {
119 let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/tests/fixtures/readonly");
120 vec![
121 root.join("001_match_return_order_by.case"),
122 root.join("002_match_expand_filter.case"),
123 root.join("003_optional_match_null_projection.case"),
124 ]
125}
126
127struct ProofCase {
128 name: String,
129 plan: Plan,
130 expected_rows: Vec<Vec<Value>>,
131}
132
133fn proof_cases() -> Vec<ProofCase> {
134 let value_col = || ColDef {
135 name: "value".to_string(),
136 kind: ColKind::Value,
137 logical_type: LogicalType::Unknown,
138 };
139 let node_col = |name: &str| ColDef {
140 name: name.to_string(),
141 kind: ColKind::Node,
142 logical_type: LogicalType::Unknown,
143 };
144 let rel_col = |name: &str| ColDef {
145 name: name.to_string(),
146 kind: ColKind::Rel,
147 logical_type: LogicalType::Unknown,
148 };
149 let version = current_plan_version("independent-consumer-proof");
150
151 vec![
152 ProofCase {
153 name: "match-return-order-by".to_string(),
154 plan: Plan {
155 version: version.clone(),
156 ops: vec![
157 Op::ScanNodes {
158 labels: vec!["Person".to_string()],
159 schema: vec![node_col("n")],
160 must_labels: vec!["Person".to_string()],
161 forbidden_labels: vec![],
162 est_rows: -1,
163 selectivity: 1.0,
164 graph_ref: None,
165 },
166 Op::Project {
167 input: 0,
168 exprs: vec![Expr::PropAccess {
169 col: 0,
170 prop: "name".to_string(),
171 }],
172 schema: vec![value_col()],
173 },
174 Op::Sort {
175 input: 1,
176 keys: vec![0],
177 dirs: vec![SortDir::Asc],
178 },
179 Op::Return { input: 2 },
180 ],
181 root_op: 3,
182 },
183 expected_rows: vec![
184 vec![Value::String("Alice".to_string())],
185 vec![Value::String("Bob".to_string())],
186 ],
187 },
188 ProofCase {
189 name: "match-expand-filter".to_string(),
190 plan: Plan {
191 version: version.clone(),
192 ops: vec![
193 Op::ScanNodes {
194 labels: vec!["Person".to_string()],
195 schema: vec![node_col("n")],
196 must_labels: vec!["Person".to_string()],
197 forbidden_labels: vec![],
198 est_rows: -1,
199 selectivity: 1.0,
200 graph_ref: None,
201 },
202 Op::Expand {
203 input: 0,
204 src_col: 0,
205 types: vec!["KNOWS".to_string()],
206 dir: plexus_serde::ExpandDir::Out,
207 schema: vec![node_col("n"), rel_col("r"), node_col("m")],
208 src_var: "n".to_string(),
209 rel_var: "r".to_string(),
210 dst_var: "m".to_string(),
211 legal_src_labels: vec!["Person".to_string()],
212 legal_dst_labels: vec!["Person".to_string()],
213 est_degree: 1.0,
214 graph_ref: None,
215 },
216 Op::Filter {
217 input: 1,
218 predicate: Expr::Cmp {
219 op: CmpOp::Gt,
220 lhs: Box::new(Expr::PropAccess {
221 col: 2,
222 prop: "age".to_string(),
223 }),
224 rhs: Box::new(Expr::IntLiteral(30)),
225 },
226 },
227 Op::Project {
228 input: 2,
229 exprs: vec![Expr::PropAccess {
230 col: 0,
231 prop: "name".to_string(),
232 }],
233 schema: vec![value_col()],
234 },
235 Op::Sort {
236 input: 3,
237 keys: vec![0],
238 dirs: vec![SortDir::Asc],
239 },
240 Op::Return { input: 4 },
241 ],
242 root_op: 5,
243 },
244 expected_rows: vec![vec![Value::String("Alice".to_string())]],
245 },
246 ProofCase {
247 name: "optional-match-null-projection".to_string(),
248 plan: Plan {
249 version,
250 ops: vec![
251 Op::ScanNodes {
252 labels: vec!["Person".to_string()],
253 schema: vec![node_col("n")],
254 must_labels: vec!["Person".to_string()],
255 forbidden_labels: vec![],
256 est_rows: -1,
257 selectivity: 1.0,
258 graph_ref: None,
259 },
260 Op::OptionalExpand {
261 input: 0,
262 src_col: 0,
263 types: vec!["WORKS_AT".to_string()],
264 dir: plexus_serde::ExpandDir::Out,
265 schema: vec![node_col("n"), rel_col("r"), node_col("c")],
266 src_var: "n".to_string(),
267 rel_var: "r".to_string(),
268 dst_var: "c".to_string(),
269 legal_src_labels: vec!["Person".to_string()],
270 legal_dst_labels: vec!["Company".to_string()],
271 graph_ref: None,
272 },
273 Op::Project {
274 input: 1,
275 exprs: vec![Expr::PropAccess {
276 col: 2,
277 prop: "name".to_string(),
278 }],
279 schema: vec![value_col()],
280 },
281 Op::Sort {
282 input: 2,
283 keys: vec![0],
284 dirs: vec![SortDir::Asc],
285 },
286 Op::Return { input: 3 },
287 ],
288 root_op: 4,
289 },
290 expected_rows: vec![vec![Value::Null], vec![Value::String("Acme".to_string())]],
291 },
292 ]
293}