Skip to main content

independent_consumer_proof/
independent_consumer_proof.rs

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}