ggen-graph 26.6.11

Deterministic graph module for ggen
Documentation
//! Deterministic RDF graph module for `ggen`.
//!
//! Provides a wrapper around Oxigraph with deterministic hashing, state change detection
//! (deltas), validation hooks, and cryptographic transition receipts.

#![deny(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic,
    clippy::todo,
    clippy::unimplemented
)]

pub mod delta;
pub mod diagnostics;
pub mod dialect;
pub mod doctor;
pub mod graph;
pub mod interchangeable;
pub mod ocel;
pub mod prelude;
pub mod receipt;
pub mod shacl;
pub mod sparql;
pub mod vocab;

pub use graph::quad::parse_nquad;
pub use graph::{
    extract_prefixes, iri_terms, parse_nquads_located, parse_ntriples_located,
    parse_turtle_located, DeterministicGraph, IriTerms, KnowledgeHook, LocatedParse,
    ParseDiagnostic, RdfDelta, TransitionReceipt,
};
pub use interchangeable::{AdapterLayer, GenesisCore, OuterMembrane, ProjectionLayer};
pub use ocel::{check_guard, check_lifecycle_order, discover_dfg, DfgEdge};
pub use shacl::{validate_shacl, ShaclSeverity, ShaclViolation};
pub use sparql::{check_sparql_syntax, sparql_kind, SparqlKind};

/// Error type for deterministic graph operations.
#[derive(Debug, thiserror::Error)]
pub enum GraphError {
    /// Errors originating from the underlying Oxigraph storage.
    #[error("Oxigraph storage error: {0}")]
    Oxigraph(#[from] oxigraph::store::StorageError),

    /// Errors originating from SPARQL query evaluation.
    #[error("SPARQL evaluation error: {0}")]
    Sparql(#[from] oxigraph::sparql::QueryEvaluationError),

    /// Errors related to RDF serialization or parsing.
    #[error("Serialization or parsing error: {0}")]
    Serialization(String),

    /// Verification failures for transition receipts.
    #[error("Receipt verification failed: {0}")]
    VerificationFailed(String),

    /// Failures in hook validation execution.
    #[error("Knowledge hook validation failed: {0}")]
    HookFailed(String),

    /// Other failures or errors.
    #[error("Other error: {0}")]
    Other(String),
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_graph_and_receipt_flow() -> Result<(), Box<dyn std::error::Error>> {
        let graph = DeterministicGraph::new()?;
        let q1_str = "<http://example.org/alice> <http://example.org/name> \"Alice\" <http://example.org/graph> .";
        let q2_str = "<http://example.org/bob> <http://example.org/name> \"Bob\" <http://example.org/graph> .";
        let q1 = parse_nquad(q1_str)?;
        let q2 = parse_nquad(q2_str)?;

        // Test insertion & contains
        graph.insert_quad(&q1)?;
        assert!(graph.contains_quad(&q1)?);
        assert!(!graph.contains_quad(&q2)?);

        // Test state hash
        let hash1 = graph.state_hash()?;

        // Test delta computation
        let target = DeterministicGraph::new()?;
        target.insert_quad(&q1)?;
        target.insert_quad(&q2)?;
        let hash2 = target.state_hash()?;

        let delta = RdfDelta::compute(&graph, &target)?;
        assert_eq!(delta.additions.len(), 1);
        assert_eq!(delta.deletions.len(), 0);
        assert_eq!(delta.additions[0], q2.to_string());

        // Test transition receipt
        let receipt = graph.apply_delta(&delta, &[])?;
        assert_eq!(receipt.pre_state_hash, hash1);
        assert_eq!(receipt.post_state_hash, hash2);
        assert_eq!(receipt.delta_hash, delta.hash());
        receipt.verify()?;

        // Verify cryptographic falsifiability
        let mut tampered_receipt = receipt.clone();
        tampered_receipt.signature_or_hash[0] ^= 1;
        assert!(tampered_receipt.verify().is_err());

        // Test validation hook success
        let hook_bob = KnowledgeHook::new(
            "has_bob".to_string(),
            "ASK WHERE { GRAPH ?g { ?s <http://example.org/name> \"Bob\" } }".to_string(),
        );
        assert!(hook_bob.execute(&graph)?);

        // Test validation hook failure (no Charlie should exist)
        let hook_no_charlie = KnowledgeHook::new(
            "no_charlie".to_string(),
            "ASK WHERE { FILTER NOT EXISTS { GRAPH ?g { ?s <http://example.org/name> \"Charlie\" } } }".to_string(),
        );
        assert!(hook_no_charlie.execute(&graph)?);

        // Try applying a delta that adds Charlie but violates the hook
        let target_charlie = DeterministicGraph::new()?;
        target_charlie.insert_quad(&q1)?;
        target_charlie.insert_quad(&q2)?;
        let q3_str = "<http://example.org/charlie> <http://example.org/name> \"Charlie\" <http://example.org/graph> .";
        let q3 = parse_nquad(q3_str)?;
        target_charlie.insert_quad(&q3)?;

        let delta_charlie = RdfDelta::compute(&graph, &target_charlie)?;
        let apply_res = graph.apply_delta(&delta_charlie, &[hook_no_charlie]);

        // Should fail due to the hook
        assert!(apply_res.is_err());

        // State should be rolled back (Charlie not present, Bob and Alice still present)
        assert!(graph.contains_quad(&q1)?);
        assert!(graph.contains_quad(&q2)?);
        assert!(!graph.contains_quad(&q3)?);
        assert_eq!(graph.state_hash()?, hash2);

        Ok(())
    }
}