Skip to main content

ggen_graph/
lib.rs

1//! Deterministic RDF graph module for `ggen`.
2//!
3//! Provides a wrapper around Oxigraph with deterministic hashing, state change detection
4//! (deltas), validation hooks, and cryptographic transition receipts.
5
6#![deny(
7    clippy::unwrap_used,
8    clippy::expect_used,
9    clippy::panic,
10    clippy::todo,
11    clippy::unimplemented
12)]
13
14pub mod delta;
15pub mod diagnostics;
16pub mod dialect;
17pub mod doctor;
18pub mod graph;
19pub mod interchangeable;
20pub mod ocel;
21pub mod prelude;
22pub mod receipt;
23pub mod shacl;
24pub mod sparql;
25pub mod vocab;
26
27pub use graph::quad::parse_nquad;
28pub use graph::{
29    extract_prefixes, iri_terms, parse_nquads_located, parse_ntriples_located,
30    parse_turtle_located, DeterministicGraph, IriTerms, KnowledgeHook, LocatedParse,
31    ParseDiagnostic, RdfDelta, TransitionReceipt,
32};
33pub use interchangeable::{AdapterLayer, GenesisCore, OuterMembrane, ProjectionLayer};
34pub use ocel::{check_guard, check_lifecycle_order, discover_dfg, DfgEdge};
35pub use shacl::{validate_shacl, ShaclSeverity, ShaclViolation};
36pub use sparql::{check_sparql_syntax, sparql_kind, SparqlKind};
37
38/// Error type for deterministic graph operations.
39#[derive(Debug, thiserror::Error)]
40pub enum GraphError {
41    /// Errors originating from the underlying Oxigraph storage.
42    #[error("Oxigraph storage error: {0}")]
43    Oxigraph(#[from] oxigraph::store::StorageError),
44
45    /// Errors originating from SPARQL query evaluation.
46    #[error("SPARQL evaluation error: {0}")]
47    Sparql(#[from] oxigraph::sparql::QueryEvaluationError),
48
49    /// Errors related to RDF serialization or parsing.
50    #[error("Serialization or parsing error: {0}")]
51    Serialization(String),
52
53    /// Verification failures for transition receipts.
54    #[error("Receipt verification failed: {0}")]
55    VerificationFailed(String),
56
57    /// Failures in hook validation execution.
58    #[error("Knowledge hook validation failed: {0}")]
59    HookFailed(String),
60
61    /// Other failures or errors.
62    #[error("Other error: {0}")]
63    Other(String),
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_graph_and_receipt_flow() -> Result<(), Box<dyn std::error::Error>> {
72        let graph = DeterministicGraph::new()?;
73        let q1_str = "<http://example.org/alice> <http://example.org/name> \"Alice\" <http://example.org/graph> .";
74        let q2_str = "<http://example.org/bob> <http://example.org/name> \"Bob\" <http://example.org/graph> .";
75        let q1 = parse_nquad(q1_str)?;
76        let q2 = parse_nquad(q2_str)?;
77
78        // Test insertion & contains
79        graph.insert_quad(&q1)?;
80        assert!(graph.contains_quad(&q1)?);
81        assert!(!graph.contains_quad(&q2)?);
82
83        // Test state hash
84        let hash1 = graph.state_hash()?;
85
86        // Test delta computation
87        let target = DeterministicGraph::new()?;
88        target.insert_quad(&q1)?;
89        target.insert_quad(&q2)?;
90        let hash2 = target.state_hash()?;
91
92        let delta = RdfDelta::compute(&graph, &target)?;
93        assert_eq!(delta.additions.len(), 1);
94        assert_eq!(delta.deletions.len(), 0);
95        assert_eq!(delta.additions[0], q2.to_string());
96
97        // Test transition receipt
98        let receipt = graph.apply_delta(&delta, &[])?;
99        assert_eq!(receipt.pre_state_hash, hash1);
100        assert_eq!(receipt.post_state_hash, hash2);
101        assert_eq!(receipt.delta_hash, delta.hash());
102        receipt.verify()?;
103
104        // Verify cryptographic falsifiability
105        let mut tampered_receipt = receipt.clone();
106        tampered_receipt.signature_or_hash[0] ^= 1;
107        assert!(tampered_receipt.verify().is_err());
108
109        // Test validation hook success
110        let hook_bob = KnowledgeHook::new(
111            "has_bob".to_string(),
112            "ASK WHERE { GRAPH ?g { ?s <http://example.org/name> \"Bob\" } }".to_string(),
113        );
114        assert!(hook_bob.execute(&graph)?);
115
116        // Test validation hook failure (no Charlie should exist)
117        let hook_no_charlie = KnowledgeHook::new(
118            "no_charlie".to_string(),
119            "ASK WHERE { FILTER NOT EXISTS { GRAPH ?g { ?s <http://example.org/name> \"Charlie\" } } }".to_string(),
120        );
121        assert!(hook_no_charlie.execute(&graph)?);
122
123        // Try applying a delta that adds Charlie but violates the hook
124        let target_charlie = DeterministicGraph::new()?;
125        target_charlie.insert_quad(&q1)?;
126        target_charlie.insert_quad(&q2)?;
127        let q3_str = "<http://example.org/charlie> <http://example.org/name> \"Charlie\" <http://example.org/graph> .";
128        let q3 = parse_nquad(q3_str)?;
129        target_charlie.insert_quad(&q3)?;
130
131        let delta_charlie = RdfDelta::compute(&graph, &target_charlie)?;
132        let apply_res = graph.apply_delta(&delta_charlie, &[hook_no_charlie]);
133
134        // Should fail due to the hook
135        assert!(apply_res.is_err());
136
137        // State should be rolled back (Charlie not present, Bob and Alice still present)
138        assert!(graph.contains_quad(&q1)?);
139        assert!(graph.contains_quad(&q2)?);
140        assert!(!graph.contains_quad(&q3)?);
141        assert_eq!(graph.state_hash()?, hash2);
142
143        Ok(())
144    }
145}